Skip to content

fix(lucidly): status() idle_target_pct must be a 0..1 fraction (was 100x off)#117

Merged
abhicris merged 1 commit into
kcolbchain:mainfrom
kite-builds:fix/lucidly-idle-target-pct-units
Jun 7, 2026
Merged

fix(lucidly): status() idle_target_pct must be a 0..1 fraction (was 100x off)#117
abhicris merged 1 commit into
kcolbchain:mainfrom
kite-builds:fix/lucidly-idle-target-pct-units

Conversation

@kite-builds

Copy link
Copy Markdown
Contributor

Problem

LucidlyAutoPark.status() reports idle_target_pct using bps / 100, so an 8000-bps target is returned as 80.0. Everywhere else in the module the same quantity is a 0..1 fraction:

location conversion 8000 bps →
LucidlyConfig.idle_target_pct (same name!) idle_target_bps / 10_000 0.8
rebalance() math target_pct = self._target_bps(chain) / 10_000 0.8
ensure_liquid() unpark_threshold_bps / 10_000
status() (this bug) ... / 100 80.0

So the public status()["idle_target_pct"] disagrees with the identically named LucidlyConfig.idle_target_pct by 100x.

Reproduction on current main:

from switchboard.adapters.lucidly import LucidlyAutoPark, LucidlyConfig
p = LucidlyAutoPark(); p.rebalance("base", 10_000.0)
p.status("base")["idle_target_pct"]   # -> 80.0
LucidlyConfig().idle_target_pct       # -> 0.8   (same name, 100x smaller)

A consumer that sizes the idle target from status() (e.g. balance * idle_target_pct) would overshoot by 100x.

Fix

Reuse the existing _target_bps() helper and the module's / 10_000 convention so status() returns the same fraction as the config property and the rebalance logic. One-line change; no behavior change to the parking math itself.

Tests

  • test_status_idle_target_pct_is_a_fraction_matching_config_convention — pins status() == config.idle_target_pct == 0.8 and 0 ≤ pct ≤ 1.
  • test_status_idle_target_pct_falls_back_to_default_bps_as_fraction — fallback path (chain absent from per_chain_targets) also returns a fraction.

pytest tests/test_lucidly.py → 15 passed. Full suite → 218 passed, 62 skipped. ruff check clean on changed files.

🤖 Generated with Claude Code

`LucidlyAutoPark.status()` returned `idle_target_pct` using `bps / 100`,
so an 8000-bps target reported as `80.0` while the rest of the module
treats this value as a 0..1 fraction:

- `LucidlyConfig.idle_target_pct` (same name) = `idle_target_bps / 10_000` -> 0.8
- `rebalance()` math: `target_pct = self._target_bps(chain) / 10_000`
- `ensure_liquid()`: `unpark_threshold_bps / 10_000`

So the public `status()["idle_target_pct"]` disagreed with the identically
named `LucidlyConfig.idle_target_pct` by 100x. A consumer reading status to
size the idle target (balance * idle_target_pct) would massively overshoot.

Fix: reuse the existing `_target_bps()` helper and the module's `/ 10_000`
convention so status() returns the same fraction as the config property and
the rebalance logic. +2 regression tests pinning the convention (default and
per-chain fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@abhicris

abhicris commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Audit verdict: safe

Legitimate bug fix (100x off-by-one in fraction conversion) with added test coverage, no malicious code, credentials, or supply-chain risks detected.

Audited by the kcolbchain PR pipeline. See pipeline docs.

@abhicris abhicris merged commit 117c49a into kcolbchain:main Jun 7, 2026
2 checks passed
@abhicris

abhicris commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Merged — thanks, @kite-builds. That's 5 merged PRs to kcolbchain now.

You're a Fellow — when paid research, partner-org work, or grant milestones come up, you're in the first invite pool.

Next-up issues across the org: https://kcolbchain.com/invitations/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants