Repository navigation
v2.0.0
Breaking changes
- BREAKING / UPGRADE:
init()now blocks, and fails by default when no
config loads (major, qfg-9p4i.8). Spec:
project/plans/2026-10-05-init-failure-design.md(sections A.3, B, C).init()blocks until the first config loads, retrying (hedged primary +
secondary, jittered backoff 0.5s, 1s, 2s, ... capped at 30s) for up to
init_timeout_ms(default 10s). A healthy boot now waits for the first
config (typically 100-300 ms) beforeinit()returns.on_init_failuretakes"fail"(the new default),"wait"or
"continue":"fail":init()raisesQuonfigInitErrorat the timeout
(reason="unavailable"), or its subclassQuonfigInitTimeoutErrorwhen
no leg answered at all (reason="timeout"). If every leg rejects the SDK
key (401/403/404/410 from every configured leg), it raises at once with
reason="unauthorized"."wait":init()keeps blocking and retrying until a config loads."continue":init()returns at the timeout; getters serve code
defaults until a config loads.
- In 1.x,
init()returned at once and the default"raise"raised lazily
from the first getter. If you relied oninit()never raising, either
catchQuonfigInitErroraroundinit()or pass
on_init_failure="continue". In every mode a client you keep after a
caughtQuonfigInitErrorkeeps retrying in the background (SSE / the
fallback poller) and heals when delivery answers. - Getters never block on init.
init()is the only gate. A getter that
runs before the first config (on another thread duringinit(), or after
a caught failure) returns the caller's default at once, or applies
on_no_defaultwhen there is none.*_detailsgetters report
reason="ERROR",error_code="PROVIDER_NOT_READY". Getters no longer
raiseQuonfigInitTimeoutError. - The 1.x spellings are deprecated aliases that log one WARN per process:
"raise"means"fail";"return"and"return_zero_value"mean
"continue". An unknownon_init_failurevalue now raisesValueError
instead of silently selecting a mode. - New readiness API:
is_ready()(a config is installed) and
wait_for_ready(timeout_ms=None) -> bool.ready()stays as an alias of
is_ready(). Under"wait"or"continue", wire your readiness probe to
is_ready(). - New
QuonfigInitErrorbase class (with.reasonand.leg_errors);
QuonfigInitTimeoutErrornow subclasses it, so existingexcept QuonfigInitTimeoutErrorblocks still catch the timeout case. - Logging follows the cross-SDK contract: a healthy boot logs only DEBUG;
the first failed attempt logs one WARN, then a WARN every 5s until the
timeout; the failure logs one ERROR naming each leg's host and error
(never the SDK key); a client still unready after init logs a WARN every
30s for 5 minutes, then every 5 minutes; the recovery logs a WARN. Records
carryevent="quonfig.init.<name>"inextra. - A forked child's first fetch applies the same
on_init_failure. An
explicitinit()in the child raises like a fresh boot; the lazy rebuild
triggered by a first getter logs the failure and serves defaults instead,
so a logging filter never raises. - Superseded: the 1.x getter-side raise from qfg-9p4i.1 below.
- Cross-SDK init conformance (qfg-9p4i.17). The init auth set is
401/403/404/410 from every configured leg, and the unauthorized line reports
the primary's status.init_timeout_ms <= 0runs exactly one attempt, then
applieson_init_failure. A secondinit()on a started client joins the
running retry instead of starting a second SSE stream and reporter that
close()did not stop. The heal line maps an unknown leg to primary, and it
is logged beforewait_for_ready()wakes. URL userinfo is stripped from init
log lines and the init error. A leg whose body stalls mid-read counts as a
hang, so an attempt that hung that way raisesreason="timeout". - Tests: integration-test-data pin bumped to
v2026.10.20(init-failure
cases); generated tests regenerated.
Fixed
-
The init deadline reason now follows the attempt in flight (qfg-9p4i.17.10,
Decisions 7 and 10). Wheninit_timeout_mscuts an attempt, that attempt
decides the reason:timeoutonly if none of its legs answered. A hung retry
after a fast 503 now raisesQuonfigInitTimeoutError, and a first attempt
cut while the primary had already failed fast raisesreason="unavailable".
In the backoff gap the last completed attempt still decides, and no new
attempt starts once the deadline has passed. -
A fast boot failure now honors
on_init_failure="raise"(patch,
qfg-9p4i.1). When every delivery leg failed fast at boot (connection
refused, DNS failure, 401, 5xx), init latched with an empty store and the
defaulton_init_failure="raise"silently served code defaults. Getters now
raiseQuonfigInitTimeoutError(the existing init error) at once, without
waiting outinit_timeout_ms, and keep raising until a config loads. The
client keeps retrying in the background over SSE / the fallback poller; once
a config installs, getters serve it. One ERROR is logged naming each leg's
host and error (never the SDK key).on_init_failure="return"/
"return_zero_value"is unchanged: it still starts on code defaults.