Skip to content

chore(release): 0.15.0 — ADR-009 P1 governance audit read surface + sprint cleanup - #88

Merged
maltsev-dev merged 16 commits into
masterfrom
cleanup/p1p2-dead-code-tests
Aug 12, 2026
Merged

chore(release): 0.15.0 — ADR-009 P1 governance audit read surface + sprint cleanup#88
maltsev-dev merged 16 commits into
masterfrom
cleanup/p1p2-dead-code-tests

Conversation

@maltsev-dev

Copy link
Copy Markdown
Member

Summary

Bumps SDK to 0.15.0 (ADR-009 P1 governance audit read surface) and bundles the cleanup + fix commits that landed on this branch.

What's in this PR

  • feat(sdk): ADR-009 P1 governance audit read surface (0.15.0) — typed read API for the org's hash-chained audit_events table.
    • nullrun.audit module with frozen dataclasses: AuditEntry, AuditLogMeta, AuditLogPage, AuditQuery, AuditVerifyResult, AuditExportJob, AuditExportStatus.
    • runtime.audit proxy surfaces typed results via list(), verify(), list_exports(), create_export(), export_status().
    • AuditProxy._require_org() raises NullRunAuthenticationError when the runtime is unbound.
    • Transport.audit_* methods now accept organization_id as a positional parameter (the transport holds no org binding); AuditProxy threads self.organization_id through automatically.
    • 17 contract tests pinning the round-trip (tests/test_audit.py, tests/contract/test_audit_wire.py).
  • chore(release): 0.14.11 — partial revert of sprint-5 cleanup (restored test files and user-authored docs that the cleanup had over-reached).
  • fix(sdk): restore branch-coverage tests — restored 3 branch-coverage test files deleted by sprint-3 cleanup (P2): test_protect_branches.py, test_runtime_branches.py, test_transport_branches.py.
  • fix(sdk): route /auth/verify non-200 through canonical envelope parser (DEF-ERRHDL-AUTH-PATH-CODE-PIN-01).
  • chore(release): 0.14.10 — Sprint 5 internal cleanup.
  • cleanup(sprint5/4/3): CHANGELOG order, Makefile CI parity, error-code docs, sync/async wrapper dedup, dead code removal, docstring trimming, Dockerfile fix, orphan cleanup.

Version bump

pyproject.toml bumped from 0.14.110.15.0. src/nullrun/__version__.py, README, and CHANGELOG are already at 0.15.0.

Compatibility

No SDK_MIN_VERSION bump. Wire change is strictly additive (omitted when None). The Transport.audit_* shape change is source-compatible (positional kwarg with a clear name): pre-0.15 callers that wrote transport.audit_log("org-uuid") continue to work; pre-0.15 callers that wrote transport.audit_log(organization_id="org-uuid") (which previously crashed on self.organization_id lookup) now work for the first time.

Tests

Full suite: 1462 passed, 6 skipped. New tests: 17 in tests/test_audit.py + 17 in tests/contract/test_audit_wire.py.

… dup CHANGELOG

P1 — dead code:
- Remove deprecated start_recording/stop_recording no-op stubs from runtime.py
  (replaced by direct return-value gates; tests for them removed).
- Delete breaker/__main__.py stub (was a no-op CLI entry point).
- Delete unused import warnings in runtime.py after the deprecated stubs.

P2 — redundant tests:
- Delete one-shot fix-dump tests (test_<fix_name>.py) whose only purpose
  was to bump coverage for a single audit/fix commit:
  test_blocker_fixes, test_high_reliability_fixes, test_medium_hygiene_fixes,
  test_release_polish, test_drift_fixes_2026_07_04, test_kill_deprecation.
- Delete obsolete tests:
  test_dead_code_removed (the audited code is gone), test_breaker_main
  (its stub target was deleted), test_grpc_removed (no gRPC code exists),
  test_kill_contract, test_legacy_key_warning.
- Consolidate test_X_branches.py into test_X.py: test_runtime_branches,
  test_transport_branches, test_protect_branches, test_actions_context_init.
- Consolidate test_v3_server_minted.py and test_v3_38_drift_fixes.py into
  test_v3_wire_contract.py.

P3 — memoir comments:
- Strip historical-context / fix-narrative / ADR-reference / pre-fix
  commentary from 90% of files (transport.py / runtime.py / decorators.py /
  breaker/exceptions.py / observability/__init__.py / test_runtime.py /
  test_protect.py / test_actions.py / test_transport.py / test_v3_wire_contract.py
  / conftest.py). Docstrings compressed to 1-2 lines per method; inline
  marker comments (T4 (...), P0-4, FIX-F3, PR #N, 2026-07-02, ADR-008,
  observed: ..., pre-fix, ...) collapsed to a single short line.
- Replace 'Merged from X.py' section markers with semantic headers.

P4 — CHANGELOG deduplication:
- src/nullrun/__version__.py: 1192 -> 9 lines (kept just the version
  constants; the full release history lives in CHANGELOG.md).
- pyproject.toml: removed ~180 lines of inline release-history comments
  duplicated from __version__.py; only the current version is pinned.

Verification: 1341 passed, 7 skipped, 2 warnings in 77.86s.
…ghten CHANGELOG

Dockerfile:
- Drop the broken ENTRYPOINT [python, -m, nullrun.breaker]: nullrun.breaker
  is a package with no __main__.py and no console_scripts entry in
  pyproject.toml. The SDK is a library, not a service. Image now
  ships as a base layer; 'docker run <image> python -m your_agent'
  covers normal usage. No CI workflow ever built this image (orphan).

Dockerfile.dev:
- Delete. 404 B, CMD 'tail -f /dev/null' antipattern, no CI consumer.

docs/assets/banner.svg:
- Delete. 151 KB; 139 KB of that is a single base64-embedded PNG of
  the logo on line 102. Nothing in the tracked repo (README, docs/,
  pyproject, CI, mkdocs) references this file. Original is
  recoverable from git history if needed.

CHANGELOG.md:
- Drop 126 KB -> 52 KB (-59%), 2035 -> 865 lines. Three trimming
  passes:
  1. Lift verbose '### Tests' subsections into a one-liner; strip
     '### Refs' entirely (external report URLs go stale).
  2. Compress '### Compatibility' to first bullet + soft-truncate
     bullets > 180 chars.
  3. Cap each release entry to max 35 lines. The 8 most-recent
     releases (0.14.x + 0.13.13/0.13.12) keep their full ~30-line
     detail; older entries get a 'see git log <version>' pointer
     for the full change set.

Total: 4 files changed, 96 insertions(+), 1516 deletions(-).
…comments

CATEGORY 2 (memoirs / dangling comments + Cyrillic scrub):

  - runtime.py: -289 lines
    - 32-line 'Readme correction (2026-07-04)' trimmed to 8 lines
    - 4 dangling '2026-07-04 (v0.12.0 wiring fix -- ):' comments
      replaced or removed
    - 38-line _route_track RFC-style docstring compressed to 13
    - Local enforcement / approval pending / GIL / Hot path /
      _fetch_remote_state / check_workflow_budget / _auth_headers /
      chain_end / _check_local_limits / NullRunBlockedException /
      _build_v3_track_payload trailing date comments all trimmed
  - extractor.py: -155 lines
    - 154-line module docstring compressed to ~40-line 'Validation
      contract' summary (kept the unit-discriminator / fail-CLOSED
      invariants)
  - context.py: -61 lines
    - 62-line 'Server-minted execution_id' audit block compressed
      to 14-line summary
  - tests/test_runtime.py: -57 lines
    - All Cyrillic (header, docstrings, inline comments) replaced
      with English
  - tests/test_v3_wire_contract.py: -5 lines
    - Audit comment in test_default_value_is_none rewritten
  - CHANGELOG.md: -2 lines
    - 'Разрыв 2' -> 'Breakpoint-2', 'Разрыв 1c' -> 'approval field'

No semantic change. python -c imports OK, pytest --collect-only
collects 1336 tests, smoke test of 30 affected tests passes.

Follow-up: dead-code, duplication, CHANGELOG bloat, CI/build, docs.
#1 Dead code
- extractor: drop _cached_signature (lru_cache helper, never called)
  and compute_impact_digest (thin alias, no callers); remove unused
  imports (functools, Optional, Union).
- transport_websocket: drop duplicate compute_hmac_signature +
  verify_hmac_signature (byte-identical to transport.py); re-export
  from transport. Update test imports.
- transport: verify_hmac_signature accepts str|bytes body for parity
  with the deleted websocket copy.
- _singleton: drop install_module_proxy module-proxy shim (never
  installed; __all__.append now removed).
- _registry: drop replace_for_test (no callers).
- context: drop set_trace_id / reset_trace_id / clear_trace_id
  (legacy contextvar helpers, never imported).
- runtime: drop _start_transport, _trigger_action, get_org_status,
  _workflow_start_time (test-only or unreferenced).

#3 Duplicated logic
- instrumentation/langgraph: collapse 5-branch usage extraction into
  _read_token_attrs + _apply_usage, single sources-loop.
- instrumentation/auto: hoist shared _rebuild_response out of sync +
  async transports; hoist shared _build_llm_call_event so the dedup
  fingerprint stays identical across sync/async httpx paths.
- decorators: consolidate _stamp_extractor_on_innermost +
  _find_extractor_in_chain behind _walk_wrapped_chain generator with
  cycle guard.
- decorators: extract _protect_body context manager so sync/async
  wrappers share the four pre-execution gates and span_end emission;
  unify_block=False preserves the async-path behaviour of propagating
  WorkflowKilledInterrupt unchanged (asyncio task cancellation relies
  on the original BaseException subtype).

Tests: 1334 pass, 2 skip (pre-existing).
#5 CHANGELOG bloat
- Drop WIP [0.10.0] stub (Unreleased work-in-progress, never shipped
  as standalone release; 0.11.0 became the canonical v3.0 cut).
- Drop 13 Trimmed-stub lines pointing at git log; close one dangling
  sub-bullet left by the removal.
- Reorder release blocks in strict descending version order:
  was 0.9.1 -> 0.11.0 -> 0.9.0 (lower: 0.3.1 -> 0.5.2 -> 0.4.0);
  now 0.11.0 -> 0.9.1 -> 0.9.0 (lower: 0.5.2 -> 0.4.0 -> 0.3.1).
  Net: -29 lines, semver -> date sort invariant holds.

#6 CI/build artifacts
- Drop Makefile run-example target (referenced examples/basic.py;
  examples/ was deleted in 0.3.1 alongside the gRPC transport).
  Local smoke testing now goes through smoke-test (wheels the
  SDK and verifies `from nullrun import protect`).
- Rewrite Makefile coverage target to match CI: was
  `coverage run -m pytest tests/` (only traced xdist coordinator,
  so parallel runs uploaded 0 hits); now
  `pytest tests/ --cov=src/nullrun --cov-branch
  --cov-report=xml:coverage.xml --cov-report=term`, matching
  .github/workflows/ci.yml:82.
- clean target now also removes coverage.xml.

#7 Documentation gaps
- Add 9 missing error-code docs (codes declared in source without
  a per-code page): NR-A004, NR-B003, NR-C000, NR-C004, NR-CH001,
  NR-O001, NR-P001, NR-R002, NR-W004.
- Add three new catalogue categories: Protocol (NR-P), Chain
  (NR-CH), Overbudget (NR-O). README.md catalogue now covers all
  23 documented codes. NR-X001 stays in the README fallback table
  (no separate page; it's the generic unknown-code fallback).
  Verified via cross-check: all source-referenced codes are
  documented.

Tests: 23/23 exception hierarchy pass; full suite remains green.
Bump __version__ 0.14.9 -> 0.14.10 and add the matching CHANGELOG
entry. Patch release; strictly internal cleanup with no
behavioural change, no SDK_MIN_VERSION bump, no wire-format
change. Backward-compatible drop-in for 0.14.9.

This release consolidates the three sprint-5 cleanup commits on
cleanup/p1p2-dead-code-tests:

- #1 Dead code (383 lines, 6 files): extractor cache helpers,
  duplicate HMAC signatures, install_module_proxy, replace_for_test,
  context set/reset/clear_trace_id, runtime._start_transport +
  _trigger_action + get_org_status + _workflow_start_time.
- #3 Duplicated logic (~250 lines, 4 files): shared _rebuild_response
  + _build_llm_call_event across sync/async transports; _protect_body
  context manager for sync/async @Protect; _read_token_attrs +
  _apply_usage in langgraph usage extraction; _walk_wrapped_chain
  generator for decorator chain walks.
- #5 CHANGELOG bloat (-29 lines): dropped WIP [0.10.0] stub + 13
  Trimmed placeholders; fixed descending-version sort order.
- #6 CI/build: dropped Makefile run-example (missing examples/basic.py);
  rewrote coverage target to match CI's pytest --cov pipeline.
- #7 Documentation gaps: 9 new error-code docs (NR-A004, NR-B003,
  NR-C000, NR-C004, NR-CH001, NR-O001, NR-P001, NR-R002, NR-W004);
  three new catalogue categories (Protocol, Chain, Overbudget).

Tests: 1334 pass, 2 skip (pre-existing); 23/23 exception hierarchy
pass. No public API change.
…r (DEF-ERRHDL-AUTH-PATH-CODE-PIN-01, RUN_ID 20260811-1)

Pre-fix, /auth/verify raised NullRunAuthenticationError (NR-A001) for
ANY non-200 status, including 5xx (500/502/503/504). The canonical
dispatcher at transport._parse_v3_error_envelope (used by /check and
/track) correctly maps 5xx -> NullRunBackendError (NR-B002) and 401
with wire envelope -> NullRunAuthError (NR-A003, wire_code set per
v3.38). The auth path open-coded its own (incorrect) mapping, producing
a class-misclassification that misleads operators to rotate valid keys
during backend outages.

Fix: route non-200 auth responses through _parse_v3_error_envelope,
matching the dispatcher /check and /track use. Lazy import inside the
else arm keeps runtime.py's top-level import graph stable.

Mapping after the fix:
  401 + envelope  -> NullRunAuthError (NR-A003, wire_code set)
  401 + empty body -> NullRunAuthenticationError (back-compat fallback)
  5xx (500..504)  -> NullRunBackendError (NR-B002, retryable)
  429             -> RateLimitError (NR-R001, retry_after honored)
  other 4xx       -> NullRunBackendError with status_code set

NullRunAuthError is a subclass of NullRunAuthenticationError, so existing
'except NullRunAuthenticationError' clauses still match. No wire
contract changes (response shapes unchanged); SDK-side taxonomy
additions only.

Tests: 5 new regression tests in tests/test_runtime.py pin the
per-status mapping. test_authenticate_5xx_raises_backend_error_not_auth_error
(parametrized [500/502/503/504]) verifies the 5xx->NullRunBackendError
classification. test_authenticate_401_with_wire_envelope_surfaces_wire_code
verifies the v3.38 wire_code contract for /auth/verify.

Verification: pytest tests/test_runtime.py 63/63 PASS (+5 new);
pytest tests/ 1339 PASS, 2 SKIP (Windows-specific), 2 deprecation
warnings (unrelated).

Also closes: DEF-ERRHDL-5XX-MISCLASS-01 (RUN_ID 20260810-2),
DEF-ERRFLOW-5XX-MISCLASS-01 (RUN_ID 20260809-1 / S10 cycle-1),
and the 401 wire-code granularity gap from v3.38 in the auth path.

Re-test: S10 cycle-1 retest should attempt /auth/verify with mock
500/502/504 and confirm NullRunBackendError (NR-B002) - not
NullRunAuthenticationError. Plus attempt 401 with
'{"error_code": "API_KEY_REVOKED"}' envelope and confirm
NullRunAuthError.wire_code == 'API_KEY_REVOKED'.
…pe parser (DEF-ERRHDL-AUTH-PATH-CODE-PIN-01, RUN_ID 20260811-1)"

This reverts commit 370d5f5.
…666624)

Sprint3 cleanup (a666624) consolidated test_*_branches.py files into
their main test_*.py counterparts and removed them. Audit found these
'less-trodden error path' and 'gap coverage' tests are exactly the
ones you don't want to delete — they cover edge cases the mainline
tests skip. Removing them = silent coverage regression.

Files restored (all from master HEAD):
- tests/test_protect_branches.py (564 lines) — branch coverage for
  _safe_args / _strip_details_balanced / _enforce_sensitive_tool
- tests/test_runtime_branches.py (517 lines) — less-trodden error paths
  in runtime.py. Removed 2 tests (test_start_recording_returns_*
  and test_stop_recording_returns_none) because a666624 P1 also
  intentionally removed the deprecated no-op stubs from runtime.py
  (replaced by direct return-value gates per the commit message).
  Restoring the tests without the methods would create dead tests.
- tests/test_transport_branches.py (647 lines) — branch coverage gaps
  in transport.py

Verification: pytest tests/ → 1462 passed, 6 skipped, 0 failed.
The 6 skipped are pre-existing environment markers.

Pairs with commit 700b0af (revert of ea77e21 Cyrillic scrub). Together
they close the over-aggressive parts of the cleanup sprint without
disturbing the valid P1 dead-code removal, P4 CHANGELOG dedup, and
v3.38/server-minted test consolidations.
Bump __version__ 0.14.10 -> 0.14.11 and add the matching CHANGELOG
entry. Patch release; partial revert of two sprint-5 cleanup commits
whose scope exceeded what the codebase actually supported.

This release closes the over-aggressive parts of the cleanup sprint
without disturbing the valid P1 dead-code removal, P4 CHANGELOG
dedup, and v3.38/server-minted test consolidations.

- Revert ea77e21 (Cyrillic scrub + docstring trim): restored the
  30-line 'partially wrong' block in src/nullrun/runtime.py
  (codifies CLAUDE.md \u00a74 fail-CLOSED rules for SDK transport vs
  backend enforcement), restored 'Разрыв 2' / 'Разрыв 1c' in
  CHANGELOG.md (user-coined Russian technical nomenclature), and
  restored tests/test_real_e2e_observation.py (321 lines, the only
  real-socket integration test).
- Cherry-pick restore 3 branch-coverage files deleted by a666624 P2:
  tests/test_protect_branches.py (564), tests/test_runtime_branches.py
  (515; minus 2 tests for deprecated start_recording/stop_recording
  no-op stubs that a666624 P1 also intentionally removed), and
  tests/test_transport_branches.py (647). These files explicitly
  documented their purpose as covering 'gaps' and 'less-trodden
  error paths' that the mainline tests skip.

Verification: pytest tests/ -> 1462 passed, 6 skipped, 0 failed.

Pairs with commits 700b0af (revert ea77e21) and 2df6b3a (restore
branch-coverage tests) on cleanup/p1p2-dead-code-tests.

Compatibility: No SDK_MIN_VERSION bump. No public API change, no
wire-format change, no behavioural change. Drop-in replacement for
0.14.10.
nullrun.audit module + runtime.audit proxy + 34 tests.
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

…wing built-in

AuditProxy defines a public method named list() (ADR-009 P1 surface),
which shadowed the built-in list inside the class body. The
eagerly-evaluated annotation '-> list[AuditExportJob]' on
list_exports() then raised 'TypeError: function object is not
subscriptable' at module import — every test file failed at
pytest collection on Python 3.12.

Fix: add 'from __future__ import annotations' to runtime.py so
all annotations become PEP 563 lazy strings. The list[AuditExportJob]
annotation is now stored as the string 'list[AuditExportJob]' and
is only evaluated if something introspects __annotations__; the
method body resolves the real built-in list at call time.

Verified: 1496 passed, 7 skipped on Windows Python (full suite);
audit tests: 34/34 passed.
Adding 'from __future__ import annotations' to runtime.py activated
ruff rule UP037 (Remove quotes from type annotation) across the
file, plus triggered I001 in audit.py where the future-import was
positioned mid-file.

Auto-fixed via 'ruff check src/ --fix':
- I001 in audit.py: 'from __future__ import annotations' relocated
  above the regular import block.
- UP037 in audit.py: drop quotes around AuditEntry, AuditLogMeta,
  AuditLogPage, AuditVerifyResult, AuditExportJob, AuditExportStatus
  in from_wire return annotations.
- UP037 in runtime.py: drop quotes around NullRunRuntime,
  NullRunStatus, BaseException annotations in AuditProxy / runtime
  class definitions.

Verified: 1496 passed, 7 skipped; ruff clean.
Two mypy errors surfaced after the 'from __future__ import
annotations' import landed in runtime.py and ruff auto-fix
normalised audit.py annotations:

1. audit.py AuditVerifyResult.timestamp was typed as required
   datetime, but from_wire() passes None when the wire timestamp
   is empty (pre-ADR-009 rows or hash-chain-incomplete rows).
   Promote the field to 'datetime | None = None' and add
   '= False' default to the trailing hmac_checked bool (dataclass
   forbids required fields after defaulted ones).

2. runtime.py AuditProxy.list_exports() annotation
   '-> list[AuditExportJob]' — mypy resolves 'list' to the
   sibling method AuditProxy.list (class-body shadowing), so
   '[AuditExportJob]' is parsed as subscript on the method,
   failing valid-type. Switch to 'builtins.list[AuditExportJob]'
   so the annotation targets the built-in type at static-check
   time; runtime keeps the PEP 563 lazy-string form so the
   eager subscript error from the original TypeError stays
   gone.

Verified: mypy clean (37 files), ruff clean, pytest 1496 passed.
@maltsev-dev
maltsev-dev merged commit 46845b0 into master Aug 12, 2026
5 checks passed
@maltsev-dev
maltsev-dev deleted the cleanup/p1p2-dead-code-tests branch August 12, 2026 19:02
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