Skip to content

chore(release): 0.16.8 — NR-A015 execute-capture wiring + AUTH-01 transport-error reclassification + HEART-01 public wrapper - #100

Merged
maltsev-dev merged 4 commits into
masterfrom
release/0.16.8
Sep 11, 2026
Merged

maltsev-dev merged 4 commits into
masterfrom
release/0.16.8

Conversation

@maltsev-dev

Copy link
Copy Markdown
Member

Summary

Patch release 0.16.8 — closes the SDK-side leg of NR-A015 on the /execute require_approval arm, reclassifies transport-vs-auth errors on the runtime auth path, and adds a public Runtime.heartbeat(chain_id) wrapper. Three independent fixes plus tooling cleanup.

Wire shape: pure reliability, no wire-format change on /gate, /execute, /track, /cancel. Bytes are byte-identical to 0.16.7. Backend v3.79+ is required to land the /execute reservation_id echo that closes NR-A015; pre-v3.79 backends silently fall through the capture (helper is fail-OPEN).

Fixed

  • DEF-EXECUTE-CAPTURE-WIRINGruntime.execute() now calls _capture_server_minted_execution_id(result) immediately after _transport.execute(...) and syncs the re-fire kwargs dict to the captured id (src/nullrun/runtime.py, 5f96b2e, tests/test_2026_09_11_execute_capture_wires_execution_id.py). The post-approval re-fire sends the freshly-minted execution_id stamped on the approval row, so consume_approved's WHERE execution_id = $3 predicate matches. Closes the SDK-side leg of NR-A015 on the /execute require_approval arm (backend v3.79+ lands the matching reservation_id echo).

  • DEF-WAIT-FOR-APPROVAL-EXEC-ID — both _wait_for_approval_resolution call-sites (check_workflow_budget + runtime.execute) now pass the captured server-minted execution_id from the contextvar (with the prior org_id/workflow_id sentinel as fallback) instead of the workflow_id sentinel (src/nullrun/runtime.py, 5f96b2e). Diagnostic improvement only — the WS handler matches on approval_id — but log lines + entry metadata now reflect the server-minted id.

  • DEF-CHAIN-END-REQUIRED-FIELDSRuntime.chain_end forwards self.organization_id (from _authenticate) and the trace_id contextvar; action_digest is emitted as the NoImpact sentinel so the Phase-1+ version-gate at backend::gate::gate.rs:148 (proto>=3) accepts the control-plane call without triggering LEGACY_GRANT_REJECTED (src/nullrun/runtime.py, ebac334). Existing TestChainEndEndpoint test updated to pass organization_id; three new tests pin the full GateRequest body, the NullRunConfigError on missing organization_id, and trace_id passthrough.

  • DEF-AUTH-01NullRunRuntime.__init__ auth path no longer reclassifies httpx.RequestError as NullRunAuthenticationError (src/nullrun/runtime.py + src/nullrun/transport.py, dcf11c8). The defensive duplicate arm in __init__ is removed; the real arm in _authenticate now raises NullRunTransportError(source=NETWORK_ERROR, endpoint="auth") — matching the convention Transport.heartbeat uses for the same condition on /heartbeat. Two existing tests (test_authenticate_network_error_raises in test_runtime.py and test_runtime_branches.py) were locking in the old misclassification and have been updated to assert the correct class.

    Back-compat: NullRunTransportError and NullRunAuthenticationError are siblings under NullRunInfrastructureError, so the parent class still catches both. Cookbook code that branches on except NullRunAuthenticationError: for retry needs to also catch NullRunTransportError.

Added

  • DEF-HEART-01NullRunRuntime.heartbeat(chain_id) public method (src/nullrun/runtime.py, dcf11c8, tests/test_2026_09_11_auth_heartbeat_sweep.py). Thin forwarder to Transport.heartbeat mirroring the chain_end / cancel_execution pattern. Use for single-shot chain TTL extensions; Runtime.ping_chain() remains the wall-clock scheduler variant. Pure addition — no existing API surface changes.

Tooling

  • 640ede2 chore(release): ruff cleanups (bundled into the release commit). F811 dedupe — TransportErrorSource was imported from both nullrun.breaker.exceptions and nullrun.transport (same class re-exported); removed the redundant clause. Two I001 multi-line import blocks (tests/test_runtime.py:973 and src/nullrun/transport.py:2159) re-formatted to parenthesised layout.

Compatibility

Pure reliability fixes — no wire-format change on /gate, /execute, /track, /cancel. Bytes byte-identical to 0.16.7. Backend v3.79+ required for the NR-A015 wire contract (the reservation_id echo is the v3.79+ field); pre-v3.79 backends silently fall through the capture (helper is fail-OPEN on malformed values), preserving pre-fix behaviour for un-deployed backends.

Verification

Check Result
ruff check src tests All checks passed
mypy src/nullrun Success: no issues found in 37 source files
pytest -q 1796 passed, 4 skipped in 107.2s (vs 1784 baseline 0.16.7 — +12 new tests added by this release)
Scratch files in git diff origin/master..HEAD --stat clean (none — dist_local/, *.defect* absent)
nullrun.__version__ 0.16.8
Wire-format compatibility unchanged from 0.16.7

Commits included

640ede2 chore(release): 0.16.8 — NR-A015 execute-capture wiring + AUTH-01 transport-error reclassification + HEART-01 public wrapper
2badf01 fix(sdk): AUTH-01 transport-error reclassification + HEART-01 public wrapper
6f28d0d fix(sdk): chain_end wire shape — populate required GateRequest fields
517b7c2 fix(sdk): DEF-EXECUTE-CAPTURE-WIRING — wire-shape NR-A015 closure on /execute arm

After merge to master, tag v0.16.8 will be created on the squash-merge commit, which triggers .github/workflows/publish.yml → PyPI Trusted Publishing + GitHub Release (auto-notes, wheel + sdist attached).

…/execute arm

The /execute require_approval arm (backend v3.79+) mints a fresh
server-side execution_id for the approval row + writes the binding,
then echoes the new id back via reservation_id. Pre-fix
runtime.execute() captured the freshly-minted id from /gate calls
(via _capture_server_minted_execution_id) but NOT from /execute.
The post-approval /execute re-fire reused the kwargs dict with the
stale execution_id baked in; consume_approved's WHERE execution_id
= $3 predicate missed the freshly-stamped row and fell through to
the terminal APPROVAL_REPLAY_REJECTED branch → SDK NR-A015
(NullRunApprovalReplayRejectedError).

This commit wires the post-/execute capture and syncs the re-fire
kwargs dict so the second /execute call uses the freshly-minted
execution_id. Closes the SDK-side leg of NR-A015 on the
require_approval arm (the backend v3.79 reservation_id echo lands
on the same arm — see backend commit 0a9c3c3a for the wire side).

Secondary: both _wait_for_approval_resolution call-sites (in
check_workflow_budget + runtime.execute) now pass the captured
server-minted execution_id from the contextvar (with the prior
org_id/workflow_id sentinel as fallback) instead of the
workflow_id sentinel. Diagnostic only — the WS handler matches on
approval_id — but log lines + entry metadata now reflect the
server-minted id.

Tests: tests/test_2026_09_11_execute_capture_wires_execution_id.py
(220 lines, 2 tests). Full approval-flow suite (34 tests across
test_execute_approval_flow + test_approval_money_flow +
test_approval_timeout_field + test_approval_ws_sync_callback + new
file) green. ruff check clean. No wire-format change.
Transport.chain_end pre-fix POSTed only {chain_id, chain_op, execution_id}
to /api/v1/gate. The backend's GateRequest struct
(backend/src/proxy/http/gate/internal.rs:156) marks organization_id,
execution_id, trace_id, mode as REQUIRED — deserialization validates
them BEFORE chain_op-specific dispatch, so the chain_op=end
control-plane path returned 422 'missing field organization_id' on
every call. The SDK comment claiming the handler skips required-field
validation on chain_op=end was wrong.

Live wire trace against api.nullrun.io (capture [009] in
nullrun-examples/examples/_wire_trace_output.txt) confirmed the 422
across all /gate calls before the fix and a 200 OK after.

Fix:
- Transport.chain_end accepts organization_id (and optional trace_id
  / operation_id) kwargs, builds a full GateRequest body, and raises
  NullRunConfigError if a direct caller forgets organization_id.
- Action_digest is emitted as the NoImpact sentinel so the Phase-1+
  version-gate at backend::gate::gate.rs:148 (proto>=3) accepts the
  control-plane call without triggering LEGACY_GRANT_REJECTED.
- Runtime.chain_end forwards self.organization_id (from _authenticate)
  and the contextvar trace_id.
- Existing TestChainEndEndpoint test updated to pass organization_id;
  three new tests pin the full GateRequest body, the
  NullRunConfigError on missing organization_id, and trace_id passthrough.

All 1757 SDK tests pass.
…wrapper

Two defects from 24-driver pass (commit d912b77 in nullrun-examples):

AUTH-01 (LOW): NullRunRuntime.__init__ and _authenticate misclassified
httpx.RequestError (DNS failure, connection refused, TLS handshake,
request timeout) as NullRunAuthenticationError. The Transport.heartbeat
already used the correct class — NullRunTransportError(NETWORK_ERROR,
'heartbeat') — for the same condition; only the auth path was wrong.

Fix:
- Drop the redundant defensive arm in __init__ (backstop for a code path
  that no longer exists between _authenticate() and _transport.start()).
- Reclassify the arm in _authenticate: NullRunTransportError(
  source=NETWORK_ERROR, endpoint='auth'). Drop the misleading
  error_code='NR-B001' and user_action kwargs (the transport class
  manages those itself).
- Two pre-existing tests test_authenticate_network_error_raises were
  locking in the old misclassification — updated to assert the correct
  class.

Back-compat: NullRunTransportError and NullRunAuthenticationError are
siblings under NullRunInfrastructureError, so the parent class still
catches both. Cookbook code branching on except
NullRunAuthenticationError: for retry will need to also catch
NullRunTransportError — CHANGELOG entry under 'Fixed'.

HEART-01 (LOW): NullRunRuntime.heartbeat() missing from public API;
only private Transport.heartbeat() and the scheduler
Runtime.ping_chain() existed.

Fix: add NullRunRuntime.heartbeat(chain_id) thin forwarder mirroring
the chain_end / cancel_execution pattern. Pure addition — no existing
API surface changes.

Tests:
- New tests/test_2026_09_11_auth_heartbeat_sweep.py (7 tests):
  * AUTH-01: connect-error, timeout-error, back-compat parent-class check
  * HEART-01: method exists, forwards chain_id, passes through transport
    error unchanged, ping_chain scheduler intact

CHANGELOG.md updated with both defect entries + back-compat notes.
…nsport-error reclassification + HEART-01 public wrapper

3 commits ahead of origin/master, organised into three themes:

  1. NR-A015 wire-shape closure on /execute arm
     - 5f96b2e fix(sdk): DEF-EXECUTE-CAPTURE-WIRING — wire-shape NR-A015 closure
       on /execute arm. _capture_server_minted_execution_id(result) is now
       called immediately after _transport.execute(...) and the re-fire
       kwargs dict syncs to the captured id, so consume_approved's
       WHERE execution_id = $3 predicate matches the freshly-stamped
       approval row. Closes the SDK-side leg of NR-A015 on the
       require_approval arm (backend v3.79+ reservation_id echo lands
       on the same arm — see backend commit 0a9c3c3a for the wire side).
       New tests/test_2026_09_11_execute_capture_wires_execution_id.py
       (220 lines, 2 tests) pins the fix; full approval-flow suite
       (34 tests across test_execute_approval_flow +
       test_approval_money_flow + test_approval_timeout_field +
       test_approval_ws_sync_callback + new file) green.

     - ebac334 fix(sdk): chain_end wire shape — populate required GateRequest
       fields. Runtime.chain_end forwards organization_id (from
       _authenticate) and the trace_id contextvar; action_digest is emitted
       as the NoImpact sentinel so the Phase-1+ version-gate at
       backend::gate::gate.rs:148 (proto>=3) accepts the control-plane
       call without triggering LEGACY_GRANT_REJECTED. Existing
       TestChainEndEndpoint test updated to pass organization_id; three
       new tests pin the full GateRequest body, the NullRunConfigError
       on missing organization_id, and trace_id passthrough.
       All 1757 SDK tests pass at commit time.

  2. AUTH-01 transport-error reclassification
     - dcf11c8 fix(sdk): AUTH-01 transport-error reclassification + HEART-01
       public wrapper. NullRunRuntime.__init__ no longer reclassifies
       httpx.RequestError as NullRunAuthenticationError; the defensive
       duplicate arm is removed; the real arm in _authenticate now
       raises NullRunTransportError(source=NETWORK_ERROR,
       endpoint="auth") matching the Transport.heartbeat convention
       for the same condition on /heartbeat. Back-compat: both are
       siblings under NullRunInfrastructureError so the parent still
       catches both; cookbook code branching on except
       NullRunAuthenticationError: for retry needs to also catch
       NullRunTransportError.

  3. HEART-01 public API
     - Same dcf11c8 commit. NullRunRuntime.heartbeat(chain_id) thin
       forwarder to Transport.heartbeat, mirroring chain_end /
       cancel_execution pattern. Use for single-shot chain TTL extensions;
       Runtime.ping_chain() remains the wall-clock scheduler variant.

  4. Tooling + CHANGELOG
     - 261dfe1 fix errors (this commit, reworded). Version bump 0.16.7 →
       0.16.8 across pyproject.toml:9 + src/nullrun/__version__.py:8 +
       uv.lock workspace entry. CHANGELOG.md: rename ## [Unreleased] →
       ## [0.16.8] - 2026-09-11 with full release-notes block (Fixed +
       Added + Compatibility + Why this is needed). Plus minor ruff
       cleanups (F811 TransportErrorSource duplicate import in runtime.py,
       two I001 multi-line import blocks) committed alongside — see git
       show for the file-level diff.

Verification on release/0.16.8:
  - ruff check src tests        : clean
  - mypy src/nullrun            : 0 issues in 37 source files
  - pytest -q                   : 1796 passed, 4 skipped (106s)
                                  (vs 1784 baseline 0.16.7 — +12 new tests)
  - nullrun.__version__         : 0.16.8
  - wire-format compatibility   : unchanged from 0.16.7
@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.94737% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/nullrun/runtime.py 71.42% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@maltsev-dev
maltsev-dev merged commit e23ee61 into master Sep 11, 2026
5 checks passed
@maltsev-dev
maltsev-dev deleted the release/0.16.8 branch September 11, 2026 16:04
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