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
57 changes: 41 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
# =============================================================================
# Release & publish workflow (v1.0 — W1.1 / W1.2 / W2.3)
#
# !!! INERT UNTIL CONFIGURED — SAFE TO MERGE !!!
#
# This workflow does NOTHING until BOTH of the following are true:
# (a) a GitHub *Release* is published (a pushed git tag ALONE does not trigger
# it — the trigger is `release: published`, the explicit human gesture), AND
# (b) the `mcp-warden-cli` PyPI project + its Trusted Publisher (OIDC) are configured
# by the owner (see RELEASING.md "One-time PyPI setup").
#
# Until (a) AND (b) hold, merging this file changes nothing at runtime: no tag is
# cut here, no version is bumped here, nothing is published here. Cutting v1.0.0 is
# then a single tag + GitHub Release away (the full runbook is in RELEASING.md).
# A published GitHub Release builds and signs artifacts. Production uploads
# additionally require the existing PyPI Trusted Publisher and enabled repo gate.
# A pushed tag alone does not trigger publication. Manual choices are TestPyPI,
# build-only, or a production authentication-only check that uploads nothing.
#
# What it does WHEN a Release is published:
# build — builds sdist + wheel, uploads them as workflow artifacts.
Expand Down Expand Up @@ -41,18 +34,23 @@ on:
release:
types: [published]

# Manual escape hatch for a dry run to TestPyPI (no Release required, no signing,
# no production PyPI). Nice-to-have; kept deliberately simple.
# Manual TestPyPI/build-only paths and an authentication-only production check.
workflow_dispatch:
inputs:
publish-target:
description: "Where to publish on a manual run"
description: "TestPyPI upload, build-only, or production OIDC verification (no upload)"
required: true
default: "testpypi"
type: choice
options:
- testpypi
- none
- verify-pypi
publisher-checked:
description: "verify-pypi only: inspected existing project publisher and no matching pending publisher in owner account"
type: boolean
required: true
default: false

# Least-privilege at the top level; each job widens scope locally only as needed.
permissions:
Expand All @@ -65,6 +63,7 @@ jobs:
# --------------------------------------------------------------------------
build:
name: Build sdist + wheel
if: github.event_name != 'workflow_dispatch' || github.event.inputs.publish-target != 'verify-pypi'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -138,7 +137,7 @@ jobs:
#
# On a `release: published` run -> production PyPI.
# On a manual `workflow_dispatch` with publish-target=testpypi -> TestPyPI only.
# publish-target=none short-circuits (build + artifacts only).
# publish-target=none builds only; verify-pypi skips the build and upload jobs.
# --------------------------------------------------------------------------
pypi-publish:
name: Publish to PyPI (OIDC Trusted Publishing)
Expand All @@ -159,7 +158,7 @@ jobs:
if: >-
vars.PYPI_TRUSTED_PUBLISHER == 'true' &&
(github.event_name == 'release' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.publish-target != 'none'))
(github.event_name == 'workflow_dispatch' && github.event.inputs.publish-target == 'testpypi'))

steps:
- name: Download dist artifacts
Expand Down Expand Up @@ -190,6 +189,32 @@ jobs:
# Re-running a dry run no-ops on an already-uploaded TestPyPI version.
skip-existing: true

# Authentication-only production check: no build, upload, signing or stored token.
# Uses this SAME workflow identity and no deployment environment, like publication.
pypi-verify:
name: Verify PyPI OIDC (no upload)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish-target == 'verify-pypi'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
WARDEN_PYPI_PUBLISHER_CONFIRMED: ${{ github.event.inputs.publisher-checked }}
steps:
- name: Require reviewed main
if: github.ref != 'refs/heads/main'
run: |
echo '::error::Production verification requires reviewed main.'
exit 1
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python 3.11
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Verify production OIDC exchange without uploading
run: python scripts/verify_pypi_oidc.py

# --------------------------------------------------------------------------
# Job 3: sign the release artifacts with Sigstore keyless ("heal thyself", W2.3).
#
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ Streamable HTTP; the v0.3 `guard` proxy adds deterministic runtime *result* insp

## [Unreleased]

### Release engineering

- Add a manual `verify-pypi` production OIDC exchange check in the existing release
workflow, without building, signing, uploading or storing credentials. Identity
mismatches, redirects, failed exchanges and malformed responses fail closed.
- Correct release documentation: existing-project publisher setup, non-reserving
pending publishers, 2.0.0's manual recovery, and unchanged-artifact failed-job reruns.

## [2.0.0] — 2026-10-02

### Breaking compatibility
Expand Down
6 changes: 4 additions & 2 deletions DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,11 @@ scope-honesty box and makes no compliance/regulatory claim.

| Doc | Purpose |
|-----|---------|
| [`RELEASING.md`](RELEASING.md) | Operator runbook: one-time PyPI Trusted-Publisher (OIDC) setup, cut-a-release checklist, post-release verification, rollback/yank. PyPI dist name is `mcp-warden-cli`; CLI/repo stay `mcp-warden`. |
| [`RELEASING.md`](RELEASING.md) | Operator runbook: existing-project PyPI publisher settings, authentication-only verification, release delivery, post-release checks, failed-job recovery and rollback/yank. PyPI dist name is `mcp-warden-cli`; CLI/repo stay `mcp-warden`. |
| [`CHANGELOG.md`](CHANGELOG.md) | Keep-a-Changelog history through CLI 2.0.0 / schema level 4 with explicit in/out-of-scope. |
| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release workflow: build Python distributions + tested TypeScript 0.2.0 tarball → publish Python to PyPI via OIDC Trusted Publishing (no stored token, **gated on repo var `PYPI_TRUSTED_PUBLISHER=true`** + `skip-existing`, #64) → Sigstore-keyless sign Python, TypeScript, and checksums and attach bundles to the Release. Live: `mcp-warden-cli` Trusted Publisher configured + the gate variable set. |
| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release: build Python + tested TypeScript tarball; upload only Python through OIDC (repo gate + `skip-existing`); Sigstore-sign artifacts/checksums. Manual `verify-pypi` exchanges credentials without build/upload/signing. The gate variable alone does not prove publisher alignment. |
| [`scripts/verify_pypi_oidc.py`](scripts/verify_pypi_oidc.py) | Standard-library OIDC probe: reviewed-main binding, owner inspection acknowledgment, bounded HTTPS, no redirects/token logs/storage/uploads. PyPI minting can mutate pending records; exchange does not prove project upload permission. |
| [`docs/plans/2026-10-02-pypi-automation-repair.md`](docs/plans/2026-10-02-pypi-automation-repair.md) | Bounded automation repair and session wrap plan, including the authenticated PyPI browser boundary. |
| [`requirements-dev.lock`](requirements-dev.lock) · [`.github/workflows/deps-locked.yml`](.github/workflows/deps-locked.yml) | **(#59)** Hash-pinned dev/CI dependency lock + the "Hash-locked dev/CI install" check (verifies `--require-hashes` install + that the lock stays in sync with `pyproject.toml` without floating to latest, #65). Dependency-update policy lives in [`SECURITY.md`](SECURITY.md). |

---
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ drifts.** `pin` and `check` support stdio and Streamable HTTP; `guard` is stdio-
destructiveHint, removing a result schema, or widening its types now triggers drift;
the existing runtime tools/list gate checks these fields against v4 locks too.
Older locks stay readable, but require review and re-pinning for this coverage.
Hints are server claims, not proof of safety, and schemas do not certify content.
Hints are server claims, not proof of safety, and schemas do not certify content. Release
operators can check production OIDC without uploading via [`RELEASING.md`](RELEASING.md).

The proposed next direction is a protocol-neutral **Warden**: human-approved tool
versions and bounded actions, with untrusted inputs kept separate from authority.
The proposed next direction is a protocol-neutral **Warden**: human-approved tool versions and bounded actions, with untrusted inputs kept separate from authority.
The [upgrade plan and checkpoint proposal](docs/plans/2026-10-02-tool-integrity-upgrade.md)
maps prompts, retrieval, code execution, and serverless adapters to existing Agent
Trust Kernel work. Those broader checkpoints are proposals, not shipped guarantees.
Expand Down
Loading
Loading