perf(quickadapter): harmonize causal availability for provably-stable zero-weight imputations (#186)
* perf(quickadapter): harmonize causal availability for provably-stable zero-weight imputations
Release trailing single-metric and combined leading non-finite runs whose
imputation is provably fixed at 0.0 under the causal prefix at their true
stabilization candle instead of the frame boundary, matching the existing
single-metric leading treatment (#158).
- Gap 1: a trailing single-metric non-finite run imputes to 0.0 and stabilizes
at the first finite pivot's confirmation; release it there. An all-non-finite
metric keeps the nonzero legacy default (1.0) and stays deferred to n, so a
dedicated trailing_stable_mask distinguishes it from dependency_mask.
- Gap 2: for combined with every component leading with a non-finite run, the
aggregate is stably zero over [0, min_c first_finite_c); release it at the
max-over-components confirmation candle (unequal run lengths must not leak),
guarded by an empirical combined_weights[:S] == 0.0 prefix-stability check.
The producer now returns a LabelWeightImputationMasks dataclass carrying both
stable masks and a stable_release_index; min bounds the stable run while max
sets the release candle, so the release index cannot be derived from the mask
alone. The non-causal path and existing single-metric leading behavior remain
bit-for-bit unchanged.
Closes #169
* refactor(quickadapter): harden causal availability release from review feedback
Address self-review findings on the #169 harmonization without changing
production behavior (bit-for-bit over a monotone battery vs the prior commit).
- Release the combined leading run via the max over the release prefix
weight_availability[: stable_release_index + 1] instead of a single lookup.
Both are equal when weight_availability is monotone (guaranteed by the zigzag
confirmation watermark), but the prefix max stays leak-free even if that
invariant is ever violated, since every contributing component confirms at or
before stable_release_index (= max_c first_finite_c for combined).
- Guard the release on idx.size == raw_idx.size so a dropped (out-of-range)
pivot, which would desync the producer-space release index from the filtered
weight_availability, conservatively defers to the frame boundary.
- Rename all_components_finite -> every_component_has_finite: the flag is true
when no component is entirely non-finite, not when every value is finite.
- Refine docstrings to state the prefix-max release and that trailing pivots
are additionally bounded by their own label availability via the max fold.
* docs(quickadapter): align LabelWeightImputationMasks docstring with prefix-max release
The dataclass docstring still described the pre-hardening single-lookup release
(weight_availability[stable_release_index]). Match the consumer/producer
docstrings: the release candle is the max over the release prefix
weight_availability[: stable_release_index + 1], where stable_release_index is
first_finite for a single metric and the max over components for combined.
* fix(quickadapter): stop causal leak of non-terminal trailing pivot weights
trailing_stable_mask marked the whole trailing non-finite run
(last_finite+1 .. n) and released it at weight_availability[first_finite]. Only
the terminal pivot (no closing swing) is provably 0.0 there: it is always the
last pivot so its trailing-boundary classification cannot flip. A non-terminal
pivot inside a longer trailing run — reachable when an interior swing metric is
non-finite (e.g. volume_rate / efficiency_ratio / volume_weighted_efficiency_
ratio on a zero-volume or flat window) — could still become interior (median,
!= 0.0) until its own later swing confirms, so releasing it at the first finite
pivot's confirmation leaked future information (under-purge).
Restrict trailing_stable_mask to the terminal pivot; a non-terminal pivot in a
length>=2 trailing run stays deferred to the frame boundary n via
dependency_mask (conservative, no leak). Behaviour is unchanged for the common
length-1 trailing run (terminal only), and for the leading, combined, and
non-causal paths. Docstrings aligned (terminal-pivot wording; stable_release_
index == -1 semantics clarified).
* docs(quickadapter): correct stable_release_index and prefix-max descriptions
Third-round review accuracy fixes (documentation only, no behaviour change):
- stable_release_index: the docstrings claimed "-1 when both stable masks are
empty", but a single metric with any finite value reports first_finite (>= 0)
even when both masks are empty (all-finite or interior-only-NaN). It is -1
only when no pivot is finite. The consumer independently gates the release on
a non-empty stable mask, so a non-negative index with empty masks is inert.
- Consumer release comment: the prefix max equals
weight_availability[stable_release_index] only under the production invariant
(zigzag confirmation watermark => monotone weight_availability), not
unconditionally; state that explicitly and note the prefix max stays leak-free
(at worst defers later) if the invariant is ever violated.
* fix(quickadapter): stop causal leak of terminal pivot-weight imputation
The terminal non-finite pivot's 0.0 imputation is not causal-prefix stable:
add_pivot backfills its swing metric once a later closing pivot arrives, so
the 0.0 becomes a nonzero interior median. Releasing it (and skipping its
Gaussian band) before the frame boundary n leaked the fact that no later
pivot occurs. Defer the terminal pivot to n via dependency_mask and remove
the now-always-empty trailing_stable plumbing (dataclass field, consumer
parameter, and caller wiring). The leading-run and combined leading release
(both provably prefix-stable) are unchanged.
Empirically verified against the prior revision: bit-for-bit availability on
all non-terminal cases; terminal cases only ever defer later (leak-free).
* refactor(quickadapter): make stable_release_index -1 when no leading run
Return stable_release_index = -1 whenever leading_stable_mask is empty
(single-metric with first_finite == 0: all-finite or interior-only-NaN),
so the field holds the invariant 'index >= 0 iff a leading run is
released' instead of relying on the consumer's mask-emptiness gate. The
consumer already gates the release on leading_stable_mask.any(), so the
availability output is bit-for-bit unchanged (verified across single,
combined, and uniform cases). Also align docstrings/terminology
(terminal vs trailing pivot) and tighten the stable_release_index docs.
The comment claimed an all-non-finite component (imputed to the nonzero
default 1.0) makes the aggregate leading run non-zero, but geometric_mean
and harmonic_mean annihilate to 0.0 when any component is 0.0 (verified),
so that rationale is false. The real reason the leading release is blocked
is that an all-non-finite component never confirms a finite weight in-frame
and thus has no first_finite release candle; the pivots defer to n
conservatively. Comment-only; no behavior change.
Remove the consumer release-prefix formula duplicated from
compute_label_weight_known_at_lookahead (single source of truth), drop the
vague 'shared' wording, and name both the producer and consumer of the
dataclass. Docstring-only; no behavior change.