Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

Stable `0.1.0` additionally requires the complete blocking Python matrix,
executed README examples against the built package, trusted live evidence, and
reviewed release-PR and changelog agreement.
reviewed release-PR and changelog agreement. Before the stable release PR is
merged, its finalization commit must state that `0.1.0` is approved for PyPI
publication, use the stable installation command and classifier, and remove the
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
project metadata, lock file, and changelog must remain at the exact generated
`0.1.0` version. If GitHub requires approval before checks run on the automated
pull request, approve only that reviewed workflow execution and wait for every
blocking check.
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ jobs:
- name: Run offline unit and contract tests
run: uv run pytest -m "not live"
- name: Check release version agreement
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
run: uv run python scripts/check_version.py --require-changelog
- name: Check canonical public content and identity
run: uv run python scripts/check_version.py --require-public-preview-docs
- name: Scan for credentials and scope mistakes
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
name: Publish immutable release

on:
release:
types:
- published
workflow_call:
inputs:
release-tag:
required: true
type: string
release-sha:
required: true
type: string
default-branch:
required: true
type: string

permissions:
contents: read
Expand All@@ -29,15 +37,16 @@ jobs:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ github.event.release.tag_name }}
ref: refs/tags/${{ inputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
DEFAULT_BRANCH: ${{ inputs.default-branch }}
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ inputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand All@@ -50,7 +59,7 @@ jobs:
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All@@ -62,7 +71,7 @@ jobs:
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/release-please.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,74 @@ jobs:
name: Maintain the reviewed release PR and release
if: vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or update the release PR, or create its approved release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"

publish-release:
name: Run the protected publication chain
needs: release-please
if: >-
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/publish.yml
with:
release-tag: ${{ needs.release-please.outputs.release-tag }}
release-sha: ${{ needs.release-please.outputs.release-sha }}
default-branch: ${{ github.event.repository.default_branch }}
32 changes: 20 additions & 12 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,9 +96,10 @@ Post-alpha invariants:
authorizes that work, without reopening completed dispositions.
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 until
a separate reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as Release Please's previous-release boundary.
`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.
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.
Expand DownExpand Up@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -242,8 +243,11 @@ committed.
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
and PyPI OIDC Trusted Publishing.
- The release commit must equal the tag target and belong to the protected
default branch. A protected live-smoke job must check out that exact commit
and succeed before the protected PyPI job can become eligible.
default branch. Release Please must independently confirm that the exact tag
and commit are immutable before directly calling the protected publication
workflow; do not rely on workflow-token release events to trigger it. A
protected live-smoke job must check out that exact commit and succeed before
the protected PyPI job can become eligible.
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
satisfy the exact-release live gate.
- Missing identity, credentials, environments, reviewers, protection,
Expand All@@ -260,12 +264,16 @@ committed.
accepted. The sole approved recovery tag is
`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 after the recovery alpha. Its manifest cannot
infer the previous-release boundary from the recovery tag's build metadata;
enabling it requires a separate reviewed and tested `last-release-sha`
bridge.
- Keep third-party Actions pinned to full commit SHAs and grant
`id-token: write` only to the publishing job.
- 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.
- 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
the OIDC token.
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
with shipped behavior. Use currently supported model IDs.
- All repository documentation is written in English.
Expand Down
9 changes: 5 additions & 4 deletions ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
```text
local mocked/package evidence
-> immutable tag commit equals checkout and belongs to protected default branch
-> release API and tag ref confirm the exact immutable identity
-> protected live-smoke job checks that exact commit
-> protected PyPI OIDC job publishes the previously verified artifact
-> public registry digest, provenance, install, import, and mocked smoke
Expand All@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
artifact identity: the PyPI version remains `0.1.0a1`.

Release Please remains disabled after this recovery release because its
manifest version does not include the recovery build metadata. A later,
separately reviewed change must establish the previous-release boundary with a
tested `last-release-sha` bridge before enabling automated release PRs.
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.

## Rejected 0.1 approaches

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,7 @@ variable, then the default:

### Chat Completions

<!-- cometapi-readme-example: sync-chat -->
```python
from cometapi import CometAPI

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

Streaming uses the official OpenAI stream type:

<!-- cometapi-readme-example: sync-chat-stream -->
```python
from cometapi import CometAPI

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

### Responses and Models

<!-- cometapi-readme-example: sync-responses-models -->
```python
from cometapi import CometAPI

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

### Async client

<!-- cometapi-readme-example: async-response -->
```python
import asyncio

Expand DownExpand Up@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand Down
54 changes: 36 additions & 18 deletions RELEASING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,10 +45,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 and remains disabled until a
separately reviewed and tested `last-release-sha` bridge establishes the
recovery alpha as the previous-release boundary. An unset or non-true value
prevents the corresponding gated job from executing.
`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.
The release live-model configuration resolves an unset or empty
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.

Expand DownExpand Up@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run pyright
uv run pytest -m "not live"
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
uv run python scripts/check_version.py --require-changelog
uv run python scripts/check_secrets.py
uv run python scripts/check_workflows.py
rm -rf dist
Expand DownExpand Up@@ -181,14 +182,18 @@ 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. Keep it disabled after the
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
lacks the recovery tag's build metadata and cannot safely infer the previous
release boundary. Enable it only after a separate reviewed change configures
and tests an explicit `last-release-sha` bridge.
- `publish.yml` runs only for a published immutable GitHub release. It resolves
the tag to the checked-out commit, fetches the protected default branch, and
rejects a commit that is not reachable from that branch. A protected
`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.
- `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
reachable from that branch. A protected
`live-smoke` job then checks out that exact verified commit and must succeed
before the protected `pypi` job becomes eligible. The workflow publishes the
previously verified artifacts with OIDC, then checks the public package
Expand All@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
or empty live-model repository variable resolves to `gpt-5.4`.

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

Expand DownExpand Up@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
and source-distribution SHA256
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
reviewed and tested `last-release-sha` bridge is merged.
- `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.

## Stable release sequence

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

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