Skip to content

Knarr v0.48.0 — The Final Gate

Choose a tag to compare

@Knarr-Forseti Knarr-Forseti released this 17 Mar 06:31
· 83 commits to main since this release

Release Notes — v0.48.0 "The Final Gate"

Date: 2026-03-16
Branch: feature/v0.48.0
Base: v0.47.0


Summary

Final formal sprint. Ships 9 spec items across test hygiene, economy, cross-node event bus, DHT plugin infrastructure, watchman reliability, and speculative routing. Five-model adversary gate (38 raw findings → 10 confirmed TPs, all fixed before promotion). The protocol is feature-complete; future work is experiment-driven.

Breaking Changes

None. All new config keys (tab_reminder_auto_netting, [recovery] defaults) are opt-in or have safe defaults.


Track A — Test Infrastructure & Hygiene

TEST-04: Stale Version Checkpoint

test_version_bumped hardcoded "0.45.0". Replaced with a semver regex (r"^\d+\.\d+\.\d+$").

TEST-02: Python 3.13 asyncio.get_event_loop() Compat

DHTNode.__init__ called asyncio.get_event_loop() without an active loop — raises DeprecationWarning (3.12) and RuntimeError (3.13) in non-async context. Fixed with asyncio.new_event_loop() + asyncio.set_event_loop(). Resolves 7 pre-existing test failures (test_cockpit_api ×4, test_result_mapping ×3).

TEST-03: get_or_create_ledger_entry Returns Wrong Balance

storage.py returned LedgerEntry(balance=initial_balance) but the DB row was always inserted with balance=0.0. Comment said "security rule: always 0.0" but the return contradicted it. Fixed to return LedgerEntry(balance=0.0).

BR-GATE-001-TEST: Unit Tests for tab_reminder WM Gate Bypass

Three-scenario test suite confirming the v0.47.0 WM bypass fix holds: (a) self-delivery path dispatches without WM rejection, (b) cross-node path dispatches without proof field, (c) signed commerce docs still route through WM normally.


Track B — Economy & Event Bus

BR-MAIL-001-EXT: Tab Reminder Auto-Netting Trigger

_maybe_send_tab_reminder now checks [settlement] tab_reminder_auto_netting (default false). When enabled and utilization exceeds tab_reminder_threshold, calls await _run_netting_cycle_if_due() automatically. Closes the credit discipline loop for high-utilization peers without operator intervention.

_run_netting_cycle_if_due promoted to async def; internal call site updated. The method's time-based guard (_if_due) prevents thundering herd.

A5-EXT: mail.flush_skip Reason Field

bus.emit("mail.flush_skip", ...) now includes reason="no_route". Makes the event self-describing for Thrall/Tyr consumers without requiring them to infer the skip cause from context.

B1-EXT: GET /api/settlements Input Validation

GET /api/settlements now returns HTTP 400 when limit or offset contain non-integer values. Valid integers (including negative) are clamped. The validation is applied in the existing _handle_settlements_list production path; total is a full COUNT query across all rows, not page count.

BUS-01: Cross-Node EventBus

New EventNotify wire message type (core/messages.py). A _bus_broadcast_loop serializes selected topics from the local bus and forwards them to all connected peers as EventNotify messages. _handle_event_notify receives, verifies, deserializes, and re-emits on the local bus.

Topic allowlist: {"mail.flush_skip"}. credit.change excluded (TP-6: bilateral financial data).

Re-broadcast prevention: outgoing EventNotify is tagged node_id=self.node_info.node_id; receive handler checks origin_node marker on re-emit to prevent loops.


Track C — DHT / Discovery

B2-EXT: Bootstrap Isolation Warning Uses Effective Peer List

_check_bootstrap_isolation now considers both [network] bootstrap config peers and runtime --bootstrap CLI args before firing the isolation warning. Eliminates false positives when bootstrap peers are supplied at runtime only.

C1-EXT: Speculative Routing for Explicit-Tier Peers

_select_execute_provider (now async def) queries both DHT skill announcements (query_skills_by_name) and the operator's explicit-tier address book (get_addresses_by_tier("explicit")). Explicit-tier peers are injected as plain candidates alongside DHT results. Only tier="explicit" entries are included — operator-declared trust, no special-casing.

Supports private cluster deployments where trusted peers are pre-configured rather than DHT-discovered.


Track D — Watchman Reliability

WM-I1: Graceful Shutdown via Sentinel File

Supervisor._terminate() writes knarr.stop to data_dir before initiating shutdown. The node process polls for the sentinel in its main loop, drains in-flight tasks, and exits cleanly. A configurable drain timeout ([watchman] drain_timeout) controls how long the supervisor waits before force-killing. Sentinel is deleted after confirmed clean drain.

WM-04: Auto-Upgrade Spawn Tracking Restored

After cmd_upgrade, self._proc is now correctly populated from the synchronous spawn path. The DETACHED_PROCESS workaround in v0.47.0 left self._proc = None, preventing crash detection and restart in the newly spawned process. Spawn tracking restored for all code paths.


Plugin Infrastructure (D-007 Phase D partial)

PluginContext extended with a node= factory path: PluginContext(node=node, plugin_name="test", send_fn=fn, data_dir=path). Wires three new callbacks: remove_peer → storage.remove_peer, upsert_address → storage.upsert_address, push_to_peer → send_fn. Legacy positional constructor unchanged.

D-007 Phase D KAD plugin not shipped. Core _peer_heartbeat_sweep_loop re-enabled (Mímir ruling 2026-03-16) pending full Phase D delivery. CR filed: D-007-D.


Adversary Gate

Five-model adversary panel (GPT-5.4, Gemini 3.1 Pro, Opus 4.6, Sonnet 4.6, Qwen 3.5). 38 raw findings → 13 unique → 10 confirmed TPs, all fixed:

ID Severity Finding
TP-1 HIGH Missing await on _run_netting_cycle_if_due in handlers.py — auto-netting silently discarded
TP-2 HIGH origin_node not stripped from EventNotify payload → bus.emit TypeError crash (remote DoS)
TP-3 HIGH _select_execute_provider called non-existent storage methods → C1-EXT dead
TP-4 MEDIUM EventNotify.origin_node_id unverified — any peer can forge origin identity
TP-5 MEDIUM Supervisor._restart() KeyError on missing [recovery] config section
TP-6 MEDIUM credit.change in broadcast topics — leaks bilateral financial data to all peers
TP-7 MEDIUM hops in SIGNATURE_EXCLUDED_FIELDS — on-path attacker can suppress any EventNotify
TP-8 MEDIUM Sentinel knarr.stop not cleaned up after drain — spurious exit on next start
TP-9 LOW _handle_settlements never registered in routes (functionally resolved by existing path)
TP-10 LOW total returned page count not full count (functionally resolved by existing path)

Test Coverage

New test files (Wave 1 + adversary suite):

  • test_event_notify_v48.py — BUS-01 EventNotify wire, re-broadcast guard, topic allowlist, TP-2/TP-6/TP-7 fixes
  • test_flush_reason_v48.py — A5-EXT reason field
  • test_settlements_400_v48.py — B1-EXT 400 validation
  • test_tab_netting_v48.py — BR-MAIL-001-EXT auto-netting trigger
  • test_wmi1_sentinel_v48.py — WM-I1 sentinel write/poll/drain/cleanup
  • test_watchman_br_gate_v48.py — BR-GATE-001-TEST three-scenario coverage
  • test_v0_32_0.py — TEST-04 semver checkpoint update

Wave 1 results (post-adversarial-fix): 54 passed, 6 skipped (D-007 KAD plugin deferred), 0 failed.


Cluster Gate

exp-1000 first-night run (2026-03-16):

  • Peer count never approached the 200-node ceiling (D-007 Phase D pre-validation)
  • mail.flush_skip observed in experiment logs (G5-EXP101 ✓)
  • Cross-node EventBus events propagating correctly (BUS-01)
  • Auto-netting trigger confirmed on threshold breach (BR-MAIL-001-EXT)

Known Issues / Deferred

Item Status
D-007 Phase D — KAD plugin on_tick sweep Deferred — CR D-007-D filed. Sweep loop re-enabled until Phase D lands.
PluginContext legacy constructor (17-arg) Deferred — factory path added; full redesign when Phase D ready
BCW _resolve_peer_public_key full table scan Deferred from v0.47.0
Route BCW deposit through Storage API Deferred from v0.47.0
BCW new-peer credit miss on first deposit Deferred from v0.47.0
M-015 Tick starvation (full fix) Deferred
Thrall gather rewire to punchhole Deferred — depends on B3 backend (Viggo)
knarr upgrade missing --data-dir flag Deferred
Relative TLS paths not rebased to data_dir Deferred
sindri.node on v0.45.0 Upgrade manually when convenient