Skip to content

v0.16.1 — Phase-1+ action_digest wire-shape fix - #92

Merged
maltsev-dev merged 3 commits into
masterfrom
release/0.16.1
Aug 20, 2026
Merged

v0.16.1 — Phase-1+ action_digest wire-shape fix#92
maltsev-dev merged 3 commits into
masterfrom
release/0.16.1

Conversation

@maltsev-dev

Copy link
Copy Markdown
Member

Summary

SDK v0.16.1 closes the Phase-1+ action_digest wire-shape gap on every /gate call. Behavior change for every @protect-decorated LLM call and any other path that goes through runtime.check_workflow_budget — those calls previously omitted action_digest and were fail-CLOSED-rejected by the backend's proto>=3 version-gate.

What changed

  • BusinessImpact.no_impact() sentinel + NoImpactPayload emitting canonical {"kind":"none"} — lets the SDK compute a deterministic NoImpact digest without inventing a fake typed impact.
  • runtime.check_workflow_budget now populates action_digest on every /gate call (was previously only forwarded on /execute via transport.execute). Per backend/src/proxy/http/gate/gate.rs:56 (v3.62.1, ADR-023 P1-6), the gate fail-CLOSED-rejects any proto>=3 client that omits the digest.
  • transport.check forwards action_digest only when truthy, so Phase-0 callers that still omit the field continue to flow through unchanged.
  • New source-pin regression test tests/test_business_impact.py::test_no_impact_digest_pins_hex pins the literal SHA-256 hex of nullrun/v1/business_impact:{"kind":"none"} — a canonicalisation drift between SDK and backend is caught at unit-test time.
  • __version__ realigned to 0.16.1 (was left at 0.15.2 after the pyproject bump in v0.16.1 prep).

Migration

Callers do not need to change anything. Every /gate site that goes through runtime.check_workflow_budget (i.e. every @protect-decorated LLM call, every explicit check_workflow_budget() call, and every with workflow(...) block) now succeeds against Phase-1+ backends.

Tests

  • 4 new NoImpact tests in tests/test_business_impact.py:
    • test_no_impact_digest_pins_hex — pinned hex literal.
    • test_no_impact_is_deterministic — two constructions produce the same digest (sentinel is a bind token).
    • test_no_impact_differs_from_money — collision-resistance against real Money digests (a collision would let a "no impact" call reuse an existing approval row's grant).
    • test_no_impact_payload_direct — pins the no-op NoImpactPayload.validate() + single-key to_wire_dict().

Compatibility

  • No SDK_MIN_VERSION bump. Wire-format is additive (action_digest is a new optional field).
  • Drop-in replacement for 0.16.0 against any Phase-1+ backend (>= v3.62.1). Phase-0 backends continue to work unchanged (transport forwards action_digest only when truthy).

Files

 CHANGELOG.md                   | +16
 pyproject.toml                 | +1 -1
 src/nullrun/__version__.py     | +1 -1
 src/nullrun/business_impact.py | +63 -1
 src/nullrun/runtime.py         | +19
 src/nullrun/transport.py       | +9
 tests/test_business_impact.py  | +61

Note on commit history

The two commits (fc926b2 version bump, 16940c1 Phase-1+ fix) were cherry-picked from fix/remove-flaky-approval-timeout-test (the branch the v0.16.0 PR #91 was developed on) onto a fresh release/0.16.1 from origin/master (6978117, post-#91-merge) — keeping master as the single integration point.

pyproject.toml was bumped to 0.16.1 in v0.16.1 prep but __version__.py was
left at 0.15.2, causing source-of-truth drift. Align __version__ to 0.16.1
so downstream tools that grep __version__ see the same value as pyproject.
Phase-1+ SDKs (proto>=3) MUST populate action_digest on every /gate call
(backend v3.62.1 / ADR-023 P1-6, gate.rs:56) — the gate fail-CLOSED-rejects
any client that omits the digest.

This release:
- Adds BusinessImpact.no_impact() sentinel + compute_action_digest for
  Phase-1+ SDKs to compute a deterministic NoImpact digest.
- Wires action_digest into check_workflow_budget so @Protect calls
  always emit a digest, even when no typed business impact is extracted.
- Adds test_business_impact.py covering canonical JSON, digest
  computation, and the NoImpact digest pinning (sha256 hex literal).
- Bumps pyproject.toml to 0.16.1.

Ref: ADR-023 P1-6, backend/src/proxy/http/gate/gate.rs:56
Ref: backend/src/proxy/http/gate/internal.rs:2687-2715 (digest re-check)
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/nullrun/business_impact.py 92.85% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

CI on PR #92 failed on ruff I001 (import-block ordering) in
src/nullrun/runtime.py:1852. Ruff wants business_impact imports
before context AND a separate from-import statement per aliased
name when a block contains more than one alias.

Pre-fix the file had a single block that combined two as-aliased
names from the same module; ruff's stricter rule requires one
import statement per aliased name. Behaviour is identical —
both names are still imported as the same locals. 1575/7skipped
pass locally before and after the change.
@maltsev-dev
maltsev-dev merged commit d7f2f2c into master Aug 20, 2026
5 checks passed
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.

1 participant