Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
87a4c2b
fix(sdk): no in-process gate cache, edge /execute never invents an al…
maltsev-dev Nov 2, 2026
5adc309
fix(sdk): APPROVAL_TOOL_MISMATCH — a money approval must bind a tool …
maltsev-dev Nov 3, 2026
db7e776
docs(sdk): point the error-envelope notes at the real catalogue
maltsev-dev Nov 4, 2026
28f1b00
fix(sdk): a gate refusal leaves the callback — only a refusal may esc…
maltsev-dev Nov 4, 2026
c4e8c4a
fix(sdk): the approval push reads the VERIFIED payload, not the outer…
maltsev-dev Nov 4, 2026
22f8d77
fix(sdk): a refused /track must reach the application (SDK-6)
maltsev-dev Nov 5, 2026
776eb01
fix(sdk): the MCP adapter derives one envelope from the tool call (SD…
maltsev-dev Nov 5, 2026
fc1666e
style(sdk): drop the redundant quotes ruff flags (SDK-7 leftover)
maltsev-dev Nov 5, 2026
b519481
fix(sdk): the fail-open switch is bounded per tool (SDK-8)
maltsev-dev Nov 5, 2026
0964705
fix(sdk): the box's answer cache is read and written under a lock (SD…
maltsev-dev Nov 5, 2026
0d1186e
test(sdk9): pin the money direction — a refusal is never a failed-open
maltsev-dev Nov 5, 2026
dc197a9
fix(sdk): a reservation id the SDK cannot bind is a REFUSAL (SDK-10)
maltsev-dev Nov 5, 2026
9dcb22d
fix(sdk): an indeterminate gate decision is a REFUSAL, not a permissi…
maltsev-dev Nov 5, 2026
5d1e5e0
fix(sdk): an unknown control-plane action is REFUSED, not silently dr…
maltsev-dev Nov 5, 2026
ac62e10
fix(sdk): an SDK-internal fault in the @protect gate is a REFUSAL (SD…
maltsev-dev Nov 5, 2026
f49aa7c
docs(sdk): NR-P004 covers the /execute seam too (SDK-13)
maltsev-dev Nov 5, 2026
4d386bb
test(sdk): the suite must drive the PRODUCTION enforcement family (SD…
maltsev-dev Nov 5, 2026
946a3a2
fix(sdk): the edge refusal reports its OWN last_source (P2-17)
maltsev-dev Nov 6, 2026
1b9ef6d
fix(sdk): every MCP tool call runs its own /gate pre-flight (P2-18)
maltsev-dev Nov 6, 2026
2a79524
fix(sdk): refuse a control-plane frame the client could have verified…
maltsev-dev Nov 6, 2026
b0f5645
fix(sdk): name the refused frame's code in the log (P2-19)
maltsev-dev Nov 6, 2026
e572403
fix(sdk): the retry clock is a seam, not the global time.sleep (P3-10)
maltsev-dev Nov 6, 2026
e1ec87c
fix(sdk): the error-code catalog names a class the dispatcher can bui…
maltsev-dev Nov 6, 2026
1bb76dd
fix(sdk): the callback obeys the same fail-OPEN/CLOSED table as @prot…
maltsev-dev Nov 7, 2026
2d19f45
fix(sdk): the chain-key refusal carries the chain class (ADR-180)
maltsev-dev Nov 8, 2026
06e8fce
build(sdk): the CI installs from the lock and pins every action (SDK-3)
maltsev-dev Nov 8, 2026
66de8f5
fix(sdk): no character of the API key reaches a log line (SDK-5)
maltsev-dev Nov 8, 2026
249df35
fix(sdk): the production detector is not a substring test (SDK-2)
maltsev-dev Nov 8, 2026
90bc05a
fix(sdk): the write-ahead log is scoped per user and per key (SDK-4)
maltsev-dev Nov 8, 2026
a4fbed2
fix(sdk): the money path asks one question: did the backend answer? (…
maltsev-dev Nov 8, 2026
6ab5a2c
fix(sdk): a failed tools/list refresh is not a policy input (SDK mino…
maltsev-dev Nov 8, 2026
c78ef32
fix(sdk): the SDK has no decision cache, and its surface says it has …
maltsev-dev Nov 8, 2026
1ad95d0
fix(sdk): redaction follows the credential, not only the argument nam…
maltsev-dev Nov 8, 2026
604cf7c
chore(release): 0.24.0 — the enforcement path stops swallowing refusals
maltsev-dev Nov 8, 2026
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
118 changes: 88 additions & 30 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@ on:
branches: [main, master]

jobs:
supply-chain:
# SDK-3 (security audit 2026-11-06). This job is the check that keeps
# the CI itself honest, and it runs first so a mutable action ref or a
# lockless dependency install fails in seconds instead of after the
# whole test matrix.
#
# The two steps:
# 1. every ``uses:`` points at a 40-hex commit SHA, and every
# dependency install comes from uv.lock;
# 2. uv.lock still matches pyproject.toml, so ``--frozen`` installs
# the versions the commit was reviewed with.
#
# The lint needs no third-party package. It reads the workflows with
# PyYAML when PyYAML is importable and with a text scanner when it is
# not, and a test proves the two scanners agree. Installing PyYAML here
# would be the very thing the lint forbids.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
# An exact uv version. "latest" would let a new uv change how the
# lock is read.
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock

- name: Check the action pins and the lock-based installs
run: python scripts/check_supply_chain.py

- name: Check that uv.lock matches pyproject.toml
run: uv lock --check

commit-message:
# The commits in this repository are authored solely by the repo owner.
# That rule is in CLAUDE.md and was still broken twice, so it is checked
Expand All @@ -31,7 +73,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
# Full history, because the range check needs the merge base with
# origin/master, and `fetch-depth: 0` is what makes that base
Expand Down Expand Up @@ -73,29 +115,34 @@ jobs:
python: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
# Cache pip's download cache keyed on the lock-relevant
# surfaces of pyproject.toml. Skips the ~60-90s cold
# install on warm caches; the action also reuses the
# cache across matrix legs when the key matches.
cache: "pip"
cache-dependency-path: pyproject.toml

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# xdist ships in the dev tree already; pin it explicitly so
# a future deps churn can't drop it without breaking CI.
# ``pytest-rerunfailures`` is used by Sprint 0 (coverage) on
# a single rare-flaky test under pytest-xdist on linux
# (thread-scheduling race in the approval-wait fixture);
# pin it for the same reason.
pip install -e ".[dev]" "pytest-xdist>=3.6" "pytest-rerunfailures>=14.0,<16.0"

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
# uv caches the resolved packages, not pip's download cache.
# The key is uv.lock, so a lock change invalidates the cache.
enable-cache: true
cache-dependency-glob: uv.lock

- name: Point uv at the interpreter setup-python installed
# Without this, uv picks its own managed interpreter and the matrix
# leg would not test the Python version it claims to test.
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"

- name: Install the dependencies from the lock
# --frozen: the lock file is the contract. The job must never
# rewrite it, and it must never resolve a version at run time.
# --extra dev: the dev extra carries pytest, pytest-xdist,
# pytest-rerunfailures, pytest-cov, ruff, mypy and the rest.
run: uv sync --frozen --extra dev

- name: Run tests
# `-n auto` lets xdist pick a worker count from the runner's
Expand All @@ -105,35 +152,46 @@ jobs:
# near single-thread baseline (no shared state between
# workers — ``reset_runtime`` autouse fixture in conftest
# is per-process by construction under xdist).
run: pytest -n auto --durations=20
#
# `uv run` puts the command inside the environment the lock
# installed, so pytest is the pinned pytest.
run: uv run --frozen --extra dev pytest -n auto --durations=20

- name: Run ruff
run: ruff check src/
run: uv run --frozen --extra dev ruff check src/

- name: Run mypy
run: mypy src/
run: uv run --frozen --extra dev mypy src/

coverage:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev]" "pytest-xdist>=3.6" "pytest-cov>=5.0" "pytest-rerunfailures>=14.0,<16.0"
- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"
- run: uv sync --frozen --extra dev
# Single Python leg for coverage — multi-version coverage
# reports don't add signal and double the runner time. 3.12
# is the modern floor for typing-only changes.
# pytest-cov starts coverage in every xdist worker and combines
# the data before producing the report. ``coverage run`` only
# traced the coordinator process, so every parallel run uploaded
# 0 hits even though all tests passed.
- run: pytest -n auto --cov=src/nullrun --cov-branch --cov-report=xml:coverage.xml --cov-report=term
- uses: codecov/codecov-action@v4
- run: uv run --frozen --extra dev pytest -n auto --cov=src/nullrun --cov-branch --cov-report=xml:coverage.xml --cov-report=term
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
58 changes: 43 additions & 15 deletions .github/workflows/publish-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,29 @@ jobs:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-python@v5
- name: Set up Python ${{ matrix.python-version }}
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: pyproject.toml

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock

- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"

- name: Install dependencies
run: pip install -e ".[dev]" "pytest-xdist>=3.6"
run: uv sync --frozen --extra dev

- name: Run tests
run: pytest tests/ -v -n auto
run: uv run --frozen --extra dev pytest tests/ -v -n auto

publish:
name: Build and publish to TestPyPI
Expand All @@ -39,24 +49,42 @@ jobs:
id-token: write

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-python@v5
- name: Set up Python
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock

- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"

# This job holds the TestPyPI Trusted Publishing token, so the
# supply-chain check runs here, before the build and the upload.
- name: Check the action pins and the lock-based installs
run: python scripts/check_supply_chain.py

- name: Check that uv.lock matches pyproject.toml
run: uv lock --check

- name: Build
run: |
pip install --upgrade build
python -m build
# ``build`` and ``twine`` come from the ``publish`` dependency
# group in uv.lock, not from a run-time pip resolution.
run: uv run --frozen --only-group publish python -m build

- name: Check dist
run: |
pip install twine
twine check dist/*
run: uv run --frozen --only-group publish twine check dist/*

- name: Publish to TestPyPI (Trusted Publishing)
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
with:
repository-url: https://test.pypi.org/legacy/
# TestPyPI rejects re-uploads of the same wheel hash with
Expand Down
66 changes: 49 additions & 17 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,36 @@ jobs:
permissions:
contents: read
# 2026-07-08: parallel matrix kept (PyPI publish is a one-shot
# event and the runner is already paid for) but pip cache
# event and the runner is already paid for) but the uv cache
# brought in for parity with ci.yml.
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-python@v5
- name: Set up Python ${{ matrix.python-version }}
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: pyproject.toml

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock

- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"

- name: Install dependencies
run: pip install -e ".[dev]" "pytest-xdist>=3.6"
run: uv sync --frozen --extra dev

- name: Run tests
run: pytest tests/ -v -n auto
run: uv run --frozen --extra dev pytest tests/ -v -n auto

publish:
name: Build and publish
Expand All @@ -50,24 +60,46 @@ jobs:
contents: write

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-python@v5
- name: Set up Python
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"

- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock

- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"

# This is the job that holds the PyPI Trusted Publishing token
# (``id-token: write`` above), so the supply-chain check belongs
# HERE and not only in ci.yml. A tag push that carries a mutable
# action ref or a lockless install must not reach the upload step.
- name: Check the action pins and the lock-based installs
run: python scripts/check_supply_chain.py

- name: Check that uv.lock matches pyproject.toml
run: uv lock --check

- name: Build
run: |
pip install --upgrade build
python -m build
# ``build`` and ``twine`` come from the ``publish`` dependency
# group in uv.lock. They used to be installed with
# ``pip install --upgrade build`` and ``pip install twine``,
# which resolved inside the trusted-publishing job.
run: uv run --frozen --only-group publish python -m build

- name: Check dist
run: |
pip install twine
twine check dist/*
run: uv run --frozen --only-group publish twine check dist/*

- name: Publish to PyPI (Trusted Publishing)
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1

# GitHub Release creation — gated on tag pushes so the manual
# workflow_dispatch path (used for hotfix re-runs) doesn't create
Expand All @@ -80,7 +112,7 @@ jobs:
# for environments where pip isn't available.
- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: ${{ github.ref_name }}
generate_release_notes: true
Expand Down
Loading
Loading