chore(release): 0.16.8 — NR-A015 execute-capture wiring + AUTH-01 transport-error reclassification + HEART-01 public wrapper - #100
Merged
Conversation
…/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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Patch release 0.16.8 — closes the SDK-side leg of NR-A015 on the
/executerequire_approvalarm, reclassifies transport-vs-auth errors on the runtime auth path, and adds a publicRuntime.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/executereservation_id echo that closes NR-A015; pre-v3.79 backends silently fall through the capture (helper is fail-OPEN).Fixed
DEF-EXECUTE-CAPTURE-WIRING —
runtime.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, soconsume_approved'sWHERE execution_id = $3predicate matches. Closes the SDK-side leg of NR-A015 on the/executerequire_approvalarm (backend v3.79+ lands the matchingreservation_idecho).DEF-WAIT-FOR-APPROVAL-EXEC-ID — both
_wait_for_approval_resolutioncall-sites (check_workflow_budget+runtime.execute) now pass the captured server-minted execution_id from the contextvar (with the priororg_id/workflow_idsentinel as fallback) instead of theworkflow_idsentinel (src/nullrun/runtime.py,5f96b2e). Diagnostic improvement only — the WS handler matches onapproval_id— but log lines + entry metadata now reflect the server-minted id.DEF-CHAIN-END-REQUIRED-FIELDS —
Runtime.chain_endforwardsself.organization_id(from_authenticate) and the trace_id contextvar;action_digestis emitted as the NoImpact sentinel so the Phase-1+ version-gate atbackend::gate::gate.rs:148(proto>=3) accepts the control-plane call without triggeringLEGACY_GRANT_REJECTED(src/nullrun/runtime.py,ebac334). ExistingTestChainEndEndpointtest updated to passorganization_id; three new tests pin the fullGateRequestbody, theNullRunConfigErroron missingorganization_id, and trace_id passthrough.DEF-AUTH-01 —
NullRunRuntime.__init__auth path no longer reclassifieshttpx.RequestErrorasNullRunAuthenticationError(src/nullrun/runtime.py+src/nullrun/transport.py,dcf11c8). The defensive duplicate arm in__init__is removed; the real arm in_authenticatenow raisesNullRunTransportError(source=NETWORK_ERROR, endpoint="auth")— matching the conventionTransport.heartbeatuses for the same condition on/heartbeat. Two existing tests (test_authenticate_network_error_raisesintest_runtime.pyandtest_runtime_branches.py) were locking in the old misclassification and have been updated to assert the correct class.Back-compat:
NullRunTransportErrorandNullRunAuthenticationErrorare siblings underNullRunInfrastructureError, so the parent class still catches both. Cookbook code that branches onexcept NullRunAuthenticationError:for retry needs to also catchNullRunTransportError.Added
NullRunRuntime.heartbeat(chain_id)public method (src/nullrun/runtime.py,dcf11c8,tests/test_2026_09_11_auth_heartbeat_sweep.py). Thin forwarder toTransport.heartbeatmirroring thechain_end/cancel_executionpattern. 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 —TransportErrorSourcewas imported from bothnullrun.breaker.exceptionsandnullrun.transport(same class re-exported); removed the redundant clause. Two I001 multi-line import blocks (tests/test_runtime.py:973andsrc/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 (thereservation_idecho 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
ruff check src testsmypy src/nullrunpytest -qgit diff origin/master..HEAD --statdist_local/,*.defect*absent)nullrun.__version__0.16.8Commits included
After merge to master, tag
v0.16.8will be created on the squash-merge commit, which triggers.github/workflows/publish.yml→ PyPI Trusted Publishing + GitHub Release (auto-notes, wheel + sdist attached).