Skip to content

Publish to PyPI with trusted publishing and check uv.lock in CI - #32

Merged
andresovela merged 2 commits into
mainfrom
pypi-trusted-publishing
Aug 12, 2026
Merged

andresovela merged 2 commits into
mainfrom
pypi-trusted-publishing

Conversation

@iCorv

@iCorv iCorv commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Release credentials. publish-python now authenticates to PyPI over OIDC trusted publishing instead of a stored API token. It gains id-token: write and drops the password: input, so PyPI verifies the repository, workflow filename, and deployment environment on each publish and no long-lived PyPI credential exists anywhere. ai-coustics-livekit-plugin is not on PyPI yet, so this is registered as a pending publisher that creates the project on first publish.

npm is deliberately left on the bootstrap token. npm only accepts a trusted publisher in the settings of a package that already exists and has no pending-publisher equivalent, so the first release still uses the NPM_TOKEN secret the workflow already expects. DEVELOPMENT.md records that sequence, including deleting the secret once the trusted publisher is configured.

Lockfile integrity. CI gains uv lock --check. npm ci already fails on a stale package-lock.json; this gives the Python side the same guarantee so both lockfiles stay trustworthy as dependency-scanner input. Aikido parses uv.lock and package-lock.json natively, and both are already committed, so no new lockfiles or requirements.txt export were needed.

Docs. DEVELOPMENT.md gains the real registry configuration and a Dependency scanning section. Adds CLAUDE.md covering repo layout, commands, cross-file architecture invariants, and the logging/parity conventions.

Pre-Landing Review

One real bug caught and fixed in this branch's own diff:

  • [CRITICAL] (confidence: 10/10) .github/workflows/ci.yml — uv lock --check was placed after uv sync --dev. uv sync re-locks when the lockfile is stale, so the check would always pass and the guard was inert. Fixed by moving it before uv sync. Verified empirically: injected a dependency into pyproject.toml only, confirmed uv lock --check fails on the drift, then confirmed a subsequent uv sync --dev makes it pass again, which is exactly the masking the original ordering would have produced.

Informational, not changed here:

  • pypa/gh-action-pypi-publish@release/v1 is a floating ref while the other third-party actions in the same file (astral-sh/setup-uv, softprops/action-gh-release) are SHA-pinned. Left as-is because PyPA recommends the floating release/v1 so attestation and OIDC changes land automatically. Worth noting that moving to OIDC reduces the blast radius here: a compromised action can no longer exfiltrate a long-lived token, only a short-lived scoped one.
  • npm audit reports 1 low-severity advisory (esbuild dev-server arbitrary file read on Windows, transitive via tsup/vitest). Pre-existing, dev-only, not shipped in the published package. Out of scope for this branch.

Adversarial Review

Reduced coverage, stated explicitly rather than silently: the Codex adversarial pass timed out after 5 minutes and produced no output, and the Claude adversarial subagent was not dispatched. Findings below are from a manual adversarial pass.

Verified by hand:

  • Job-level permissions: replaces the workflow-level default entirely, so contents: read is restated alongside id-token: write; nothing is silently dropped.
  • The OIDC claims the trusted publisher must match are all present and correct: repository ai-coustics/livekit-plugins, workflow filename release.yml, environment publish. The publish environment already exists and is gated to *.*.* tags, which matches the release trigger.
  • uv lock --check now runs as the first uv command, before any virtualenv exists. Re-verified in a clean clone with a cold cache to confirm the reordering does not depend on a prior uv sync.
  • Guard scope is honest: uv lock --check catches manifest/lock drift, not dependency tampering. The docs claim only drift.

Test plan

  • Python unit tests pass (56 passed)
  • Node unit tests pass (47 passed, 4 files)
  • ruff check / ruff format --check clean
  • mypy clean (7 source files)
  • tsc --noEmit clean, tsup build succeeds
  • Both workflow files parse as valid YAML
  • uv lock --check and npm ci confirm neither lockfile has drifted

Release-path publishing itself cannot be tested before merge, since it only runs on a pushed tag.

Required before the next release tag

Merging this alone is not sufficient. The release will fail at publish-python unless the pending publisher is registered first.

  1. Register a PyPI pending publisher at https://pypi.org/manage/account/publishing/ with project ai-coustics-livekit-plugin, owner ai-coustics, repository livekit-plugins, workflow release.yml, environment publish. Note it does not reserve the name, and the project is created owned by the account that registers it.
  2. Add an NPM_TOKEN granular access token (read+write on the @ai-coustics scope) to the publish environment for the first npm publish only.
  3. After the first successful release, configure the npm trusted publisher on the package and delete NPM_TOKEN.

🤖 Generated with Claude Code

iCorv and others added 2 commits August 12, 2026 16:20
Documents the two mirrored Python/Node packages, the test layer split and
its env vars, the cross-file invariants (loaded-model API, lazy Processor
format init, shared VAD inference via frame userdata, fail-open), and the
logging and parity conventions. Points at DEVELOPMENT.md as the
authoritative long-form reference rather than duplicating it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The publish-python job now authenticates over OIDC instead of a stored
PYPI_API_TOKEN: it gains id-token: write and drops the password input, so
PyPI verifies the repository, workflow file, and publish environment and no
long-lived PyPI credential exists. The project is not yet on PyPI, so this
is registered as a pending publisher that creates it on first publish.

npm has no pending-publisher equivalent and only accepts a trusted
publisher on an existing package, so the first release still uses the
NPM_TOKEN bootstrap secret the workflow already expects. DEVELOPMENT.md
records that sequence and that the secret is deleted afterwards.

CI also gains uv lock --check. It runs before uv sync --dev, which would
otherwise re-lock and mask the drift it is meant to catch. npm ci already
fails on a stale package-lock.json, so this gives the Python side the same
guarantee and keeps both lockfiles trustworthy as dependency-scanner input.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@iCorv
iCorv requested a review from andresovela August 12, 2026 14:29
@iCorv iCorv self-assigned this Aug 12, 2026
@iCorv

iCorv commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@andresovela both pending publisher and NPM token have been configured. Running the publish pipeline should work.

@andresovela
andresovela merged commit 1889fa0 into main Aug 12, 2026
8 checks passed
@andresovela
andresovela deleted the pypi-trusted-publishing branch August 12, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants