Skip to content

release: 0.7.0 — thin-client refactor#34

Merged
maltsev-dev merged 1 commit into
masterfrom
release/0.7.0
Jun 26, 2026
Merged

release: 0.7.0 — thin-client refactor#34
maltsev-dev merged 1 commit into
masterfrom
release/0.7.0

Conversation

@maltsev-dev

Copy link
Copy Markdown
Member

BREAKING CHANGES

SDK is now a thin client. All enforcement decisions arrive from the backend via /api/v1/gate and /api/v1/execute. Local policy enforcement, its dataclass, and its hardcoded thresholds are removed.

Removed:

  • class Policy, Policy.default_local(), Policy.strict_local(), Policy.from_dict() (was at nullrun.runtime.Policy)
  • NullRunRuntime.policy property
  • NullRunRuntime(policy=...) constructor kwarg
  • NullRunStatus.active_policy, .fallback_policy, .fallback_reason, .last_policy_fetch, .last_policy_fetch_age_seconds fields
  • Transport.fetch_policy() method
  • Transport.clear_policy_cache() method
  • FallbackMode.CACHED enum value
  • Local loop/rate detectors: LoopTracker, RateTracker, LocalDecision classes
  • NullRunRuntime._local_check(), _loop_tracker, _rate_tracker instance attrs
  • _local_loop_threshold, _local_rate_limit (hardcoded 6/1000)
  • CachedDecision, PolicyCache transport classes
  • NULLRUN_FALLBACK_MODE env var
  • NULLRUN_POLICY_FAIL_OPEN env var (backend is authoritative)
  • NullRunRuntime._fetch_policy() method
  • WS on_policy_invalidated callback

Migration: if you need to display policy values in a UI, fetch them directly via GET /api/v1/orgs/{org_id}/policies. The SDK no longer mirrors them.

Audit: Drift D-01 from 2026-06-26 SDK↔backend audit (PolicyResponse lacked fields SDK expected; local defaults silently widened limits).

Transport finalizer behavior change

Transport._atexit_flush_safe is now a no-op that emits a single DEBUG log line. It does NOT persist buffered events to the WAL anymore — by the time weakref.finalize fires, self._buffer / self._lock / self._client are already gone. Crash-safety now lives exclusively in stop() and the context-manager pattern. Callers who relied on the implicit on-exit WAL flush MUST switch to:

with nullrun.Transport(api_url=..., api_key=...) as t:
    ...

or call t.stop() explicitly before process exit.

Tests

  • tests/test_signal_safety.py::TestAtexitViaWeakref rewritten to pin the new no-op-finalizer contract (was written against an intended but-unimplemented WAL-persist finalizer).
  • tests/test_deprecation_warnings.py removed (NULLRUN_FALLBACK_MODE env var is gone; deprecation warning is moot).
  • tests/test_no_local_policy.py added (pins absence of NullRunRuntime._local_check and the local Policy dataclass).
  • Various test updates reflecting runtime/transport refactors (-1883 / +1432 in tests/, mostly deletions of policy- and fallback-mode-specific cases).

Other

  • pyproject.toml: version bumped 0.6.1 -> 0.7.0 (was inconsistent with version.py before this commit).
  • CHANGELOG.md: full 0.7.0 entry covering BREAKING CHANGES, the transport-finalizer contract change, and migration guidance.

Verification (local on Windows / Python 3.14.2):
pytest 913 passed, 13 skipped (0:08:50)
ruff check clean on src/ and tests/
mypy src/ clean on 26 source files

What

Why

How

Test plan

  • Unit tests pass (per-repo, e.g. cd backend && cargo test, cd frontend && npm test)
  • Lint passes (per-repo, e.g. cd frontend && npm run lint)
  • Type-check passes (per-repo, e.g. cd frontend && npm run type-check)
  • Manually verified in dev / staging

Risk

Checklist

  • I have read the repo's CONTRIBUTING.md (if present)
  • My change does not introduce new lint warnings
  • I have updated the CHANGELOG (if user-visible)
  • I have considered backwards compatibility

BREAKING CHANGES
----------------

SDK is now a thin client. All enforcement decisions arrive from the
backend via /api/v1/gate and /api/v1/execute. Local policy
enforcement, its dataclass, and its hardcoded thresholds are removed.

Removed:
  * class Policy, Policy.default_local(), Policy.strict_local(),
    Policy.from_dict() (was at nullrun.runtime.Policy)
  * NullRunRuntime.policy property
  * NullRunRuntime(policy=...) constructor kwarg
  * NullRunStatus.active_policy, .fallback_policy,
    .fallback_reason, .last_policy_fetch,
    .last_policy_fetch_age_seconds fields
  * Transport.fetch_policy() method
  * Transport.clear_policy_cache() method
  * FallbackMode.CACHED enum value
  * Local loop/rate detectors: LoopTracker, RateTracker,
    LocalDecision classes
  * NullRunRuntime._local_check(), _loop_tracker, _rate_tracker
    instance attrs
  * _local_loop_threshold, _local_rate_limit (hardcoded 6/1000)
  * CachedDecision, PolicyCache transport classes
  * NULLRUN_FALLBACK_MODE env var
  * NULLRUN_POLICY_FAIL_OPEN env var (backend is authoritative)
  * NullRunRuntime._fetch_policy() method
  * WS on_policy_invalidated callback

Migration: if you need to display policy values in a UI, fetch them
directly via GET /api/v1/orgs/{org_id}/policies. The SDK no longer
mirrors them.

Audit: Drift D-01 from 2026-06-26 SDK↔backend audit
(PolicyResponse lacked fields SDK expected; local defaults silently
widened limits).

Transport finalizer behavior change
-----------------------------------

Transport._atexit_flush_safe is now a no-op that emits a single
DEBUG log line. It does NOT persist buffered events to the WAL
anymore — by the time weakref.finalize fires, self._buffer /
self._lock / self._client are already gone. Crash-safety now lives
exclusively in stop() and the context-manager pattern. Callers who
relied on the implicit on-exit WAL flush MUST switch to:

    with nullrun.Transport(api_url=..., api_key=...) as t:
        ...

or call t.stop() explicitly before process exit.

Tests
-----

* tests/test_signal_safety.py::TestAtexitViaWeakref rewritten to
  pin the new no-op-finalizer contract (was written against an
  intended but-unimplemented WAL-persist finalizer).
* tests/test_deprecation_warnings.py removed (NULLRUN_FALLBACK_MODE
  env var is gone; deprecation warning is moot).
* tests/test_no_local_policy.py added (pins absence of
  NullRunRuntime._local_check and the local Policy dataclass).
* Various test updates reflecting runtime/transport refactors
  (-1883 / +1432 in tests/, mostly deletions of policy- and
  fallback-mode-specific cases).

Other
-----

* pyproject.toml: version bumped 0.6.1 -> 0.7.0 (was inconsistent
  with __version__.py before this commit).
* CHANGELOG.md: full 0.7.0 entry covering BREAKING CHANGES, the
  transport-finalizer contract change, and migration guidance.

Verification (local on Windows / Python 3.14.2):
  pytest        913 passed, 13 skipped (0:08:50)
  ruff check    clean on src/ and tests/
  mypy src/     clean on 26 source files
@maltsev-dev
maltsev-dev merged commit b0dea8d into master Jun 26, 2026
4 checks passed
@maltsev-dev
maltsev-dev deleted the release/0.7.0 branch June 26, 2026 10:10
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.55556% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/nullrun/__init__.py 14.28% 5 Missing and 1 partial ⚠️
src/nullrun/transport_websocket.py 66.66% 4 Missing and 1 partial ⚠️
src/nullrun/transport.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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