Skip to content

Commit 4ccae83

Browse files
committed
chore(release): 0.14.9 - v3.38 wire-drift close (capabilities route, API_KEY_* codes, soft_pass decision)
1 parent d63eb27 commit 4ccae83

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,39 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

88
---
99

10+
## [0.14.9] - 2026-08-07
11+
12+
v3.38 wire-drift close — three real contract bugs that diverged from backend source code. Verified against `backend/src/proxy/http/protocol.rs`, `backend/src/proxy/middleware/auth.rs`, and CLAUDE.md §5 / §13 — not against comments or documentation. No SDK_MIN_VERSION bump. No on-wire change (backend already shipped the matching wire shape; this SDK release closes the consumer side).
13+
14+
### Fixed
15+
16+
- **Capabilities probe route**`nullrun.capabilities.CAPABILITIES_PATH` was `"/health"` (a generic liveness endpoint) instead of the canonical `"/api/v1/capabilities"`. Pre-fix, every `init()` probe returned `None` and `is_v3_ready()` was always `False`, so every v3 capability flag (`server_minted_execution_id` / `per_execution_reservations` / `enforcement_modes_soft` / `heartbeat_time_based`) was a runtime no-op — even when the backend was v3-ready. The new probe URL matches `backend/src/proxy/http/protocol.rs::capabilities_handler` (canonical wire contract since 2025-04).
17+
- **API_KEY_* error code granularity (v3.38 backend split)** — backend v3.38 split the `API_KEY_REVOKED` bucket into five distinct wire codes: `API_KEY_EXPIRED` / `API_KEY_DISABLED` / `API_KEY_INVALID` / `API_KEY_MISSING` / `API_KEY_MALFORMED` (mirrors CLAUDE.md §13 vocabulary). Pre-fix, only `API_KEY_REVOKED` was mapped in `_V3_ERROR_CODE_MAP`; the other five silently fell through to the generic HTTP-status fallback at `transport.py:~2616` and never surfaced as `NullRunAuthError`, losing both the exception class and the diagnostic `wire_code`. The map now covers all six wire codes. The envelope parser filters unknown `details` keys to a known kwargs set (`{error_code, user_action, retryable, docs_url, cause}`) and parks extras on `self.details` — the pre-fix behaviour was to forward every detail as a kwarg and raise `TypeError` on the first unknown key (the regression appeared once v3.38 EXPIRED responses started emitting `expires_at` in details).
18+
- **`NullRunAuthError.wire_code`** — the exception class gains a `wire_code: str | None = None` constructor kwarg that defaults to `"API_KEY_REVOKED"` for backwards compat. Mirrors the existing `NullRunChainError.backend_code` pattern at `breaker/exceptions.py:448`. Handlers can now branch on the granular lifecycle signal instead of inferring from message strings.
19+
20+
### Added
21+
22+
- **`decision == "soft_pass"` handler in `check_workflow_budget`** — the runtime's `/gate` decision dispatcher gains a `soft_pass` branch (currently the only branch missing from the source). Pre-fix the branch was absent, so soft-mode calls that proceeded via the chain's overdraft cap fell through the default allow path with no log line and no `soft_overdraft_used` counter increment — silent budget drift. The new branch:
23+
- calls `metrics.inc_runtime("soft_overdraft_used")` so the dashboard can graph soft-cap pressure
24+
- logs at WARNING with `overdraft_used_cents` / `max_overdraft_cents` / `remaining_overdraft_cents` from the backend response so operators can see which chains are burning overdraft
25+
- returns normally (the `allow` semantic is correct — the gate already authorised the call via the chain's overdraft cap)
26+
27+
### Tests
28+
29+
- `tests/test_v3_38_drift_fixes.py` — 14 new regression tests across three classes:
30+
- `CAPABILITIES_PATH` is `"/api/v1/capabilities"` (constant pin); probe against canonical route with v3 payload yields `is_v3_ready() == True` (negative pin against `/health` mocks).
31+
- `_V3_ERROR_CODE_MAP` covers all six wire codes (6-case parametrise); `NullRunAuthError.wire_code` surfaces the granular backend code (default to `API_KEY_REVOKED`); envelope parser filters unknown details without raising `TypeError`.
32+
- Static-source scan pins the `soft_pass` branch structure (counter increment, WARNING log, `overdraft_used_cents` reference) — mirroring the `migration_drift_tests` pattern used elsewhere in the SDK and backend. A future refactor that drops the branch fails the test in CI rather than at first production `/check`.
33+
- `tests/conftest.py` / `tests/test_capabilities.py` / `tests/test_init_contract.py` updated to mock `/api/v1/capabilities` (was `/health`).
34+
35+
### Compatibility
36+
37+
- **No SDK_MIN_VERSION bump.** All three fixes are consumer-side; the backend already shipped the matching wire shape.
38+
- **No public API change.** `CAPABILITIES_PATH` / `_V3_ERROR_CODE_MAP` / `NullRunAuthError` are internal implementation details; the public surface (`nullrun.init(...)`, `@protect`, `decision`-keyed `GateResponse` parsing) is unchanged.
39+
- **Test suite: 1457 passed, 7 skipped** (no regressions from the wire-drift close; pre-fix the affected tests were passing on the wrong-shape mock responses).
40+
41+
---
42+
1043
## [0.14.8] - 2026-08-06
1144

1245
Execution Graph v0 — additive sub-agent lineage. The backend landed `parent_execution_id` as an optional wire field on `/api/v1/gate` (backend commit `87fae759`, not pushed yet) so an SDK spawning a sub-agent can name the parent's `execution_id`. Backend validates ownership against the parent's `execution:{id}` Redis binding (mirrors the `/cancel` ownership check) and rejects cross-org / cross-key / not-found with `403 PARENT_EXECUTION_*`. This release ships the SDK-side forward path, the matching capability flag, and the three-way error-code mapping. Wire change is strictly additive (omitted when `None`); no SDK_MIN_VERSION bump.

pyproject.toml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,32 @@ name = "nullrun"
152152
# startup. The strip normalises the value before storage so the
153153
# HMAC signing path and the Authorization header see the same
154154
# canonical form on both sides of the wire.
155-
version = "0.14.7"
155+
# 0.14.9 (2026-08-07): v3.38 wire-drift close — three real
156+
# contract bugs that diverged from backend source. (1)
157+
# ``nullrun.capabilities.CAPABILITIES_PATH`` was ``/health`` (a
158+
# generic liveness endpoint) instead of the canonical
159+
# ``/api/v1/capabilities``; pre-fix every ``init()`` probe
160+
# returned None and ``is_v3_ready()`` was always False, leaving
161+
# the v3 capability flags as runtime no-ops. (2) Backend v3.38
162+
# split the ``API_KEY_REVOKED`` bucket into five distinct wire
163+
# codes (``API_KEY_EXPIRED`` / ``API_KEY_DISABLED`` /
164+
# ``API_KEY_INVALID`` / ``API_KEY_MISSING`` /
165+
# ``API_KEY_MALFORMED``) — pre-fix only ``API_KEY_REVOKED`` was
166+
# mapped in ``_V3_ERROR_CODE_MAP``, so the other five silently
167+
# fell through to the generic HTTP-status fallback and never
168+
# surfaced as ``NullRunAuthError``, losing both the exception
169+
# class and the diagnostic ``wire_code``. (3) Backend returns
170+
# ``decision == "soft_pass"`` for soft-mode calls that proceed
171+
# via the chain's overdraft cap (CLAUDE.md §5); pre-fix
172+
# ``check_workflow_budget`` had no branch for ``soft_pass`` and
173+
# it fell through the default allow path with no log line and
174+
# no ``soft_overdraft_used`` counter increment — silent budget
175+
# drift. The new soft_pass branch increments the counter via
176+
# ``metrics.inc_runtime("soft_overdraft_used")`` and logs at
177+
# WARNING with ``overdraft_used_cents`` so operators have
178+
# visibility into which chains are burning overdraft. Three
179+
# real bugs closed; no SDK_MIN_VERSION bump; no on-wire change.
180+
version = "0.14.9"
156181
# Kept under the 200-char preview threshold so the full line is visible
157182
# without an "expand" click. The headline is the canonical §1 statement
158183
# from positioning.md — "runtime decision layer for tool-using AI agents"

src/nullrun/__version__.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
"""NullRun Platform SDK.
22
3+
v3.38 / 0.14.9 (2026-08-07) — wire-drift close: three real
4+
contract bugs that diverged from backend source code.
5+
(1) ``nullrun.capabilities.CAPABILITIES_PATH`` was ``/health``
6+
(legacy liveness endpoint) instead of the canonical
7+
``/api/v1/capabilities``. Pre-fix every ``init()`` probe
8+
returned None and ``is_v3_ready()`` was always False, leaving
9+
the v3 capability flags as runtime no-ops.
10+
(2) Backend v3.38 split the ``API_KEY_REVOKED`` bucket into
11+
five distinct wire codes (``API_KEY_EXPIRED`` /
12+
``API_KEY_DISABLED`` / ``API_KEY_INVALID`` /
13+
``API_KEY_MISSING`` / ``API_KEY_MALFORMED``) — pre-fix only
14+
``API_KEY_REVOKED`` was mapped in ``_V3_ERROR_CODE_MAP``, so
15+
the other five silently fell through to the generic
16+
HTTP-status fallback and never surfaced as
17+
``NullRunAuthError``, losing both the exception class and the
18+
diagnostic ``wire_code``.
19+
(3) Backend returns ``decision == "soft_pass"`` for soft-mode
20+
calls that proceed via the chain's overdraft cap (CLAUDE.md
21+
§5); pre-fix ``check_workflow_budget`` had no branch for
22+
``soft_pass`` and it fell through the default allow path with
23+
no log line and no ``soft_overdraft_used`` counter increment
24+
— silent budget drift. The new soft_pass branch increments
25+
the counter via ``metrics.inc_runtime("soft_overdraft_used")``
26+
and logs at WARNING with ``overdraft_used_cents`` so
27+
operators have visibility into which chains are burning
28+
overdraft.
29+
Recommended upgrade path: 0.14.8 -> 0.14.9 (or 0.14.7 -> 0.14.9).
30+
331
v3.31.6 / 0.14.7 (2026-08-04) — init contract hardening: strip
432
whitespace from ``api_key`` before the truthiness check.
533
@@ -1160,5 +1188,5 @@
11601188
11611189
"""
11621190

1163-
__version__ = "0.14.7"
1191+
__version__ = "0.14.9"
11641192
__platform_version__ = "1.0.0"

0 commit comments

Comments
 (0)