You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(sdk): make Author field non-empty + v3 capability probe (#49)
* feat(sdk): server-minted execution_id default — uuid7 + capability probe
Task #3 / Task #18 (2026-07-03): wire the SDK to the backend's
v3 default. Per CLAUDE.md §24, every /check mints a
server-side uuidv7 execution_id; the SDK receives it in the
response and propagates it to /track.
This is the SDK_MIN_VERSION for the v3 rollout per CLAUDE.md
§0 pre-flip checklist.
Changes:
src/nullrun/uuid7.py (new):
- RFC 9562 §5.7 time-ordered ID generator. 48-bit unix_ts_ms
prefix + 12-bit rand_a + 62-bit rand_b. Same layout as the
backend's mint_execution_id() so log scrapers can sort by
ID alone.
- Uses secrets.token_bytes(10) for cryptographically secure
random component.
- uuid7() returns stdlib UUID; uuid7_str() returns the
canonical 36-char string.
src/nullrun/capabilities.py (new):
- ServerCapabilities dataclass mirrors /health payload.
- is_v3_ready() returns True only when ALL three v3 caps
(server_minted_execution_id, per_execution_reservations,
heartbeat_time_based) are set.
- probe_capabilities(api_url) — best-effort /health fetch
with 2s timeout. Returns None on failure (not fatal).
- validate_sdk_version(sdk_version, caps) — returns warnings
for SDK_MIN_VERSION mismatch.
- SDK_MIN_VERSION_FOR_V3 = '0.12.0' is the gate's coordinate
for the v3 rollout.
src/nullrun/__init__.py:
- init() now probes /health after singleton registration and
logs a startup warning for version mismatch (does NOT fail
init() — the gate still rejects with PROTOCOL_TOO_OLD).
- Probe is best-effort: timeout/5xx logs at INFO.
src/nullrun/__version__.py:
- Bumped 0.11.0 → 0.12.0 (the SDK_MIN_VERSION coordinate).
CHANGELOG.md:
- New 0.12.0 entry with Added/Changed sections.
tests/test_uuid7.py (new): 8 tests pin the wire contract:
- Returns stdlib UUID
- 36-char string format
- Version bits = 7
- Variant bits = 0b10
- Time-ordered (consecutive calls sort)
- 1000 unique IDs under rapid calls
- Round-trips through uuid.UUID()
tests/test_capabilities.py (new): 9 tests pin:
- v3-ready backend parses to is_v3_ready()=True
- Missing keys default to False (fail-closed)
- Partial v3 caps → not ready
- Old SDK against v3 backend → warning
- Current SDK → no warning
- Legacy backend → 'not v3-ready' warning
- Unparseable versions don't crash
- as_dict() is wire-safe (no secrets)
- SDK_MIN_VERSION_FOR_V3 = '0.12.0'
Tests: 17 new SDK tests pass. Full backend test suite still
green at 1443.
* fix(sdk): populate Author/Author-email via metadata hook
PEP 621 maps `authors` to PKG-INFO's `Author-email:` line but not to the
legacy single `Author:` line that `pip show` renders, and pip does
not display `Maintainer:` either. As a result every previous release
shipped with an empty `Author:` and the maintainer's name never
appeared in `pip show nullrun`.
Hatchling compounds this: its authors parser only adds an entry to
`authors_data["name"]` (which becomes `Author:`) when an
inline-table has a `name` and NO `email`. When both are present the
name is folded into `Author-email:`'s display_name and the legacy
`Author:` line is suppressed entirely.
Fix: declare `authors` and `maintainers` as dynamic fields and
populate them from a custom hatchling metadata hook
(`hatch_build.py`). The hook splits the primary author into a
name-only + email-only inline-table pair so hatchling populates both
`Author:` and `Author-email:`. Declaring at least one dynamic field
is what actually wires `MetadataHookInterface.update()` — without it
hatchling configures the hook but never invokes it.
* fix(sdk): bind logger in init() and cover capability probe paths
CI on Python 3.11 failed with `NameError: name 'logger' is not defined`
in 5 tests. The `feat(sdk)` commit (2a7886b) added new
`logger.warning/info/debug` calls in `init()` after the existing
`import logging` but never assigned the `logger` name. Master
passed only because its pre-existing `logger.warning` calls sit
inside an `if existing is not None:` branch that tests rarely
exercise; the new ones run on every `init()` call.
Also covers the 9 newly-uncovered lines Codecov flagged:
`probe_capabilities` failure paths (non-2xx / ConnectError /
malformed JSON) and the four new `init()` logging branches
(`debug=True` sets DEBUG; probe unreachable → INFO; probe raises
→ DEBUG; existing runtime shutdown raises → WARNING).
Local verification (.venv-ci, Python 3.14):
- pytest: 1154 passed (was 1129; +25 new)
- ruff: clean
- mypy: clean
- coverage: 82.02% (threshold 82.00%)
* style: reorder capability probe imports per ruff I001
Ruff's isort rule flagged the import block in `init()` — the
`from nullrun.__version__` line was placed after
`from nullrun.capabilities` but `__version__` sorts before
`capabilities` (underscore is 0x5F, letters are 0x61+), so the
correct alphabetical order is reversed.
CI `Run ruff` step was failing on this; the previous commit's
ruff output was checked against an outdated working copy.
Server-minted execution_id default ON. Per CLAUDE.md section 24, every /check now mints a server-side uuidv7 execution_id. The SDK no longer needs to generate its own; the response carries the server-minted id which propagates to /track. This is the SDK_MIN_VERSION for the v3 rollout - older SDKs still work for v1/v2 endpoints but should upgrade.
14
+
15
+
### Added
16
+
17
+
-`nullrun.uuid7` module - RFC 9562 section 5.7 time-ordered ID generator. Used internally for trace_id and span IDs.
18
+
-`nullrun.capabilities` module - probe_capabilities(), parse_capabilities(), validate_sdk_version(). Wired into nullrun.init().
19
+
20
+
### Changed
21
+
22
+
-__version__ bumped from 0.11.0 to 0.12.0.
23
+
11
24
## [0.9.1] - 2026-06-29
12
25
26
+
### Added
27
+
28
+
-`nullrun.uuid7` module - RFC 9562 section 5.7 time-ordered ID generator. Used internally for trace_id and span IDs.
29
+
-`nullrun.capabilities` module - probe_capabilities(), parse_capabilities(), validate_sdk_version(). Wired into nullrun.init().
30
+
31
+
### Changed
32
+
33
+
-__version__ bumped from 0.11.0 to 0.12.0.
34
+
13
35
Patch on top of 0.9.0. Unifies the LLM-call fingerprint scheme so the
14
36
dedup LRU at `runtime.track()` can collapse sibling emissions from the
15
37
httpx transport and the LangChain callback for the same real call.
0 commit comments