diff --git a/CHANGELOG.md b/CHANGELOG.md index 572e93a..e900dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 28 reference-impl unit tests pin every clause of `spec/evidence-grade.md` § 3, lifting the suite from 2315 → 2343 tests. +### Changed (v0.3.0 candidate) + +- **`threshold_derivation` is now quantity-aware (RESISTANCE vs WORK).** + The helper previously offered only the resistance positions + (`LOWER` / `TARGET` / `UPPER`, `1/3 … 1/φ²`), so deriving a WORK + quantity (buffer capacity, rate budget, queue depth) from `TARGET` + under-provisioned it by ~20%. Added `BandPosition.WORK_TARGET` + (work-zone midpoint ~0.441) + `WORK_CEILING` (0.50 pivot) and the + `derive_work_target` / `derive_work_target_float` helpers; documented + that sustained-vs-spike is the caller's `SustainedLoadTracker` call, + not a static derivation. The `redis-rate-limiter` adoption guide now + derives its sustained refill rate from the work zone. + ### Added (v0.3.0 candidate) - **Multi-scale observation Protocol** ([`spec/multi-scale.md`](spec/multi-scale.md)) diff --git a/docs/adoption/redis-rate-limiter.md b/docs/adoption/redis-rate-limiter.md index cf475d1..95df217 100644 --- a/docs/adoption/redis-rate-limiter.md +++ b/docs/adoption/redis-rate-limiter.md @@ -22,8 +22,7 @@ from substrate import ResistanceBandConfig from substrate.threshold_derivation import ( derive_hard_limit, derive_soft_limit, - derive_target, - derive_threshold_float, + derive_work_target_float, ) @@ -38,6 +37,9 @@ class BandLimiter: can absorb a burst; above it, the limiter starts charging. - ``hard_limit`` (band upper edge × capacity) — above this, requests are rejected. + - sustained ``refill`` rate (work-zone target × capacity) — the + throughput the limiter sustains is a WORK quantity, so it targets the + work zone (~0.441), not the resistance setpoint. Limit *override* policy follows the band: tighter is permitted, looser is rejected at construction. @@ -55,8 +57,10 @@ class BandLimiter: self._prefix = key_prefix self._soft = float(derive_soft_limit(int(capacity), config=band)) self._hard = float(derive_hard_limit(int(capacity), config=band)) - # The refill rate aims at the band's midpoint as a closed-loop target. - self._refill_per_sec = derive_threshold_float(capacity, config=band) + # The sustained refill rate is a WORK quantity (throughput the + # system carries), so it targets the work-zone midpoint (~0.441), + # not the resistance setpoint (~0.358). + self._refill_per_sec = derive_work_target_float(capacity, config=band) @property def soft_limit(self) -> float: @@ -85,7 +89,7 @@ class BandLimiter: ## What the band buys -- **Commensurability across limiters.** A `BandLimiter(capacity=1000)` for one upstream and a `BandLimiter(capacity=10_000)` for another have the same *shape* — both run at `~33–38%` of capacity. Operators reasoning about either limiter use the same mental model. +- **Commensurability across limiters.** A `BandLimiter(capacity=1000)` for one upstream and a `BandLimiter(capacity=10_000)` for another have the same *shape* — both sustain throughput in the work zone (`~38–50%` of capacity, the carried-load band) with the same burst/reject edges. Operators reasoning about either limiter use the same mental model. - **Single-knob tightening.** When operations needs to shed load globally (e.g., a degraded downstream), tightening the band config propagates to every limiter that derives from it. No per-limiter migration. - **Refusal of unsafe widening.** A subsystem that proposes a `lower_bound=0.20, upper_bound=0.50` configuration is *refused at construction* (the package rejects loosening past defaults). The operator surface gets the error early, not when the limiter is in production. diff --git a/python/src/substrate/threshold_derivation.py b/python/src/substrate/threshold_derivation.py index 7208bbb..bb6bfd4 100644 --- a/python/src/substrate/threshold_derivation.py +++ b/python/src/substrate/threshold_derivation.py @@ -2,21 +2,43 @@ When choosing thresholds (rate limits, ring sizes, batch sizes, retry caps, queue depths, soft / hard quotas), this module derives them from -the productive-resistance band rather than from ad-hoc multipliers. This -is the single source of truth for that derivation across consumers. +the substrate band rather than from ad-hoc multipliers. This is the +single source of truth for that derivation across consumers. -Three reference points along the band: +**Declare the quantity — RESISTANCE vs WORK — before deriving.** The two +have different bands, and conflating them inverts the model: -- **LOWER** (~``1/3``) — the entry to the productive band. Below this - the system is under-loaded. -- **TARGET** (midpoint, ~``0.358``) — closed-loop steady-state. The - point the resistance band's recommended scaling factor walks toward. -- **UPPER** (~``1/φ² ≈ 0.382``) — the exit of the productive band. - Above this the system is stressed and should shed load. +- **RESISTANCE** (imposed challenge / pull-back) calibrates in the + ``1/3 … 1/φ²`` band — use the RESISTANCE positions (LOWER / TARGET / + UPPER) for friction-type thresholds (retry caps, backpressure entry). +- **WORK** (the load a subsystem CARRIES — buffer capacity, processing + rate, queue depth, steady-state quota) cruises the WORK ZONE + ``1/φ² … 0.50`` — use the WORK positions (WORK_TARGET / WORK_CEILING). + Deriving a WORK quantity from the resistance TARGET (~0.358) + UNDER-provisions it by ~20%; ``0.45`` is the work zone, not a failure. + +RESISTANCE reference points: + +- **LOWER** (~``1/3``) — productive-band entry; below = under-loaded. +- **TARGET** (midpoint, ~``0.358``) — resistance-band steady-state (the + imposed-challenge setpoint). +- **UPPER** (~``1/φ² ≈ 0.382``) — productive-band exit; above, the + challenge eats into the maintained capacity. + +WORK reference points (the sustainable cruise): + +- **WORK_TARGET** (~``0.441``) — the work-zone midpoint; the steady-state + a carried-load quantity should provision toward. +- **WORK_CEILING** (``0.50``) — work-zone top / half-period pivot; the + ceiling of indefinitely-sustainable WORK (peaking above it is + transient-only). A capacity ``C`` is bound to threshold ``C × band_position``, so the band's anchor flows mechanically into operational thresholds without -operators rolling their own multipliers. +operators rolling their own multipliers. Whether an excursion above the +chosen position is an absorbed spike or accruing debt is a *temporal* +determination that belongs to the caller's ``SustainedLoadTracker``, not +to this static derivation. """ from __future__ import annotations @@ -25,17 +47,28 @@ from substrate.resistance_band import ( DEFAULT_CONFIG, + WORK_ZONE_UPPER, ResistanceBandAssessment, ResistanceBandConfig, assess, ) class BandPosition(str, Enum): - """Three reference points along the productive-resistance band.""" + """Reference points for threshold derivation, keyed by quantity. + RESISTANCE (imposed challenge): LOWER / TARGET / UPPER span 1/3 … 1/φ². + WORK (carried load): WORK_TARGET / WORK_CEILING span 1/φ² … 0.50. + Pick by the quantity you are deriving — deriving a WORK quantity from + a RESISTANCE position under-provisions it. + """ + + # RESISTANCE positions — imposed-challenge setpoint (1/3 … 1/φ²) LOWER = "lower" TARGET = "target" UPPER = "upper" + # WORK positions — the sustainable cruise (1/φ² … 0.50) + WORK_TARGET = "work_target" + WORK_CEILING = "work_ceiling" #: Default minimum threshold floor — derived thresholds never go below #: this even when the input capacity is tiny. Prevents pathological @@ -52,6 +85,12 @@ def _band_position( return cfg.lower_bound if position is BandPosition.UPPER: return cfg.upper_bound + if position is BandPosition.WORK_CEILING: + return WORK_ZONE_UPPER + if position is BandPosition.WORK_TARGET: + # work-zone midpoint: halfway between the resistance ceiling + # (1/φ²) and the work-zone top (the 0.50 half-period pivot). + return (cfg.upper_bound + WORK_ZONE_UPPER) / 2.0 return cfg.target def derive_threshold( @@ -89,6 +128,39 @@ def derive_threshold_float( fraction = _band_position(position, config=config) return capacity * fraction +def derive_work_target( + capacity: float, + *, + config: Optional[ResistanceBandConfig] = None, + min_threshold: int = DEFAULT_MIN_THRESHOLD, +) -> int: + """Return the steady-state WORK provisioning (work-zone midpoint × capacity). + + Use for load a subsystem CARRIES — buffer capacity, queue depth, + per-source rate budget. The sustainable cruise for WORK is the work + zone (1/φ² … 0.50), so the steady-state target is its midpoint + (~0.441 × capacity), NOT the resistance ``TARGET`` (~0.358). Deriving + a WORK quantity from the resistance target under-provisions it. + """ + return derive_threshold( + capacity, position=BandPosition.WORK_TARGET, + config=config, min_threshold=min_threshold, + ) + +def derive_work_target_float( + capacity: float, + *, + config: Optional[ResistanceBandConfig] = None, +) -> float: + """Return the steady-state WORK target as a float (no rounding). + + The continuous-quantity companion of :func:`derive_work_target` — for + rate budgets (requests/sec, frames/sec, bytes/sec) the system sustains. + """ + return derive_threshold_float( + capacity, position=BandPosition.WORK_TARGET, config=config, + ) + def derive_soft_limit( capacity: float, *, @@ -194,4 +266,6 @@ def assess_utilization( "derive_target", "derive_threshold", "derive_threshold_float", + "derive_work_target", + "derive_work_target_float", ] diff --git a/python/tests/test_threshold_derivation.py b/python/tests/test_threshold_derivation.py index b6e693b..47bfa62 100644 --- a/python/tests/test_threshold_derivation.py +++ b/python/tests/test_threshold_derivation.py @@ -8,6 +8,7 @@ from substrate.resistance_band import ( LOWER_BOUND, UPPER_BOUND, + WORK_ZONE_UPPER, ResistanceBandClassification, ResistanceBandConfig, ) @@ -23,9 +24,49 @@ derive_target, derive_threshold, derive_threshold_float, + derive_work_target, + derive_work_target_float, ) +_WORK_TARGET_FRACTION = (UPPER_BOUND + WORK_ZONE_UPPER) / 2.0 + + +class TestDeriveWorkTarget: + def test_work_target_at_work_zone_midpoint(self) -> None: + assert derive_work_target(1024) == int(1024 * _WORK_TARGET_FRACTION) + + def test_work_target_exceeds_resistance_target(self) -> None: + # Carried-load provisioning must exceed the resistance setpoint + # (~0.441 vs ~0.358). + assert derive_work_target(1024) > derive_target(1024) + + def test_work_target_position_matches_helper(self) -> None: + assert derive_work_target(1000) == derive_threshold( + 1000, position=BandPosition.WORK_TARGET, + ) + + def test_work_ceiling_is_pivot(self) -> None: + assert derive_threshold( + 1000, position=BandPosition.WORK_CEILING, + ) == int(1000 * WORK_ZONE_UPPER) + + def test_work_target_within_work_zone(self) -> None: + assert UPPER_BOUND < _WORK_TARGET_FRACTION < WORK_ZONE_UPPER + + def test_work_target_float_no_floor(self) -> None: + assert derive_work_target_float(100.0) == pytest.approx( + 100.0 * _WORK_TARGET_FRACTION + ) + + def test_work_target_min_floor(self) -> None: + assert derive_work_target(1) == DEFAULT_MIN_THRESHOLD + + def test_work_target_negative_raises(self) -> None: + with pytest.raises(ValueError, match="capacity"): + derive_work_target(-1) + + class TestDeriveThreshold: def test_target_at_band_midpoint(self) -> None: # capacity 1000 → band target ~0.358 → ~358