diff --git a/bench/fixtures/t3-bound/known-answer-tests.jsonl b/bench/fixtures/t3-bound/known-answer-tests.jsonl index 1edcd2e..0fcd64e 100644 --- a/bench/fixtures/t3-bound/known-answer-tests.jsonl +++ b/bench/fixtures/t3-bound/known-answer-tests.jsonl @@ -1,12 +1,16 @@ # T3 bound calculator known-answer tests — version t3-bound-v1-bottou-refinement -# Generated 2026-05-10 from docs/soft-hash-channel-t3-bound.md §7 -# worked examples + override-constant variants + edge regimes. -# Algorithm change MUST bump CALCULATOR_VERSION + emit new fixture. -{"label": "small-deployment-\u00a77.1", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024}, "expected_total": 625.8716, "expected_b1": 292.4813, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5} -{"label": "medium-deployment-\u00a77.2", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 1000, "lr_grid_size": 16, "window_length": 100000, "batches_per_epoch": 8192, "steps_per_epoch": 8192}, "expected_total": 3387.72, "expected_b1": 2924.8125, "expected_b2": 400.0, "expected_b3": 62.9075, "expected_snr_grad": 0.5} -{"label": "hardened-deployment-\u00a77.3", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.01, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 4, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024}, "expected_total": 247.1407, "expected_b1": 13.7504, "expected_b2": 200.0, "expected_b3": 33.3904, "expected_snr_grad": 0.1} -{"label": "extreme-low-g", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.001, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.5, "lr_decision_interval": 500, "lr_grid_size": 2, "window_length": 5000, "batches_per_epoch": 256, "steps_per_epoch": 256}, "expected_total": 80.0144, "expected_b1": 0.0144, "expected_b2": 10.0, "expected_b3": 70.0, "expected_snr_grad": 0.002} -{"label": "tight-window-W=100", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 2.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 50, "lr_grid_size": 4, "window_length": 100, "batches_per_epoch": 64, "steps_per_epoch": 64}, "expected_total": 10.6781, "expected_b1": 5.0, "expected_b2": 4.0, "expected_b3": 1.6781, "expected_snr_grad": 1.0} -{"label": "tightened-c-b1", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 0.5}, "expected_total": 479.631, "expected_b1": 146.2406, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5} -{"label": "all-constants-tight", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 0.1, "c_b2": 0.1, "c_b3": 0.1}, "expected_total": 62.5872, "expected_b1": 29.2481, "expected_b2": 30.0, "expected_b3": 3.339, "expected_snr_grad": 0.5} -{"label": "b3-floor-regime", "calculator_version": "t3-bound-v1-bottou-refinement", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 100.0, "gradient_noise_stddev": 0.001, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 4, "steps_per_epoch": 4}, "expected_total": 6444.0004, "expected_b1": 6144.0004, "expected_b2": 300.0, "expected_b3": 0.0, "expected_snr_grad": 5000.0} +# Regenerated 2026-05-11 after the b1_model=max_envelope default change +# (dav1d review Option B applied in v1). Algorithm change MUST bump +# CALCULATOR_VERSION + emit a new fixture file. +{"label": "small-deployment-§7.1", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 6183.0154, "expected_b1": 5849.625, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 1729.7158, "expected_b1_aggregate_bias": 5849.625, "expected_b1_effective_control_v1": 292.4813, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "medium-deployment-§7.2", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 1000, "lr_grid_size": 16, "window_length": 100000, "batches_per_epoch": 8192, "steps_per_epoch": 8192, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 58959.1575, "expected_b1": 58496.2501, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 17297.1581, "expected_b1_aggregate_bias": 58496.2501, "expected_b1_effective_control_v1": 2924.8125, "expected_b2": 400.0, "expected_b3": 62.9075, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "hardened-deployment-§7.3", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.01, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 4, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 1608.4256, "expected_b1": 1375.0352, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 345.9432, "expected_b1_aggregate_bias": 1375.0352, "expected_b1_effective_control_v1": 13.7504, "expected_b2": 200.0, "expected_b3": 33.3904, "expected_snr_grad": 0.1, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "extreme-low-g", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.001, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.5, "lr_decision_interval": 500, "lr_grid_size": 2, "window_length": 5000, "batches_per_epoch": 256, "steps_per_epoch": 256, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 94.4125, "expected_b1": 14.4125, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 7.9248, "expected_b1_aggregate_bias": 14.4125, "expected_b1_effective_control_v1": 0.0144, "expected_b2": 10.0, "expected_b3": 70.0, "expected_snr_grad": 0.002, "expected_snr_per_channel": 2.0, "expected_certification_status": "CERTIFIED_BY_BOUND"} +{"label": "tight-window-W=100", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 2.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 50, "lr_grid_size": 4, "window_length": 100, "batches_per_epoch": 64, "steps_per_epoch": 64, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 105.6781, "expected_b1": 100.0, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 21.9616, "expected_b1_aggregate_bias": 100.0, "expected_b1_effective_control_v1": 5.0, "expected_b2": 4.0, "expected_b3": 1.6781, "expected_snr_grad": 1.0, "expected_snr_per_channel": 20.0, "expected_certification_status": "CERTIFIED_BY_BOUND"} +{"label": "tightened-c-b1", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 0.5, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 3258.2029, "expected_b1": 2924.8125, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 864.8579, "expected_b1_aggregate_bias": 2924.8125, "expected_b1_effective_control_v1": 146.2406, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "all-constants-tight", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 0.1, "c_b2": 0.1, "c_b3": 0.1, "b1_model": "max_envelope"}, "expected_total": 618.3015, "expected_b1": 584.9625, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 172.9716, "expected_b1_aggregate_bias": 584.9625, "expected_b1_effective_control_v1": 29.2481, "expected_b2": 30.0, "expected_b3": 3.339, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "b3-floor-regime", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 100.0, "gradient_noise_stddev": 0.001, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 4, "steps_per_epoch": 4, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 123180.0089, "expected_b1": 122880.0089, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 8304.8275, "expected_b1_aggregate_bias": 122880.0089, "expected_b1_effective_control_v1": 6144.0004, "expected_b2": 300.0, "expected_b3": 0.0, "expected_snr_grad": 5000.0, "expected_snr_per_channel": 100000.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "gradient-fraction-zero", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "max_envelope", "inputs": {"gradient_fraction": 0.0, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "max_envelope"}, "expected_total": 333.3904, "expected_b1": 0.0, "expected_b1_selected": "fraction_channels", "expected_b1_fraction_channels": 0.0, "expected_b1_aggregate_bias": 0.0, "expected_b1_effective_control_v1": 0.0, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.0, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "b1-effective-control-v1-mode", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "effective_control_v1", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "effective_control_v1"}, "expected_total": 625.8716, "expected_b1": 292.4813, "expected_b1_selected": "effective_control_v1", "expected_b1_fraction_channels": 1729.7158, "expected_b1_aggregate_bias": 5849.625, "expected_b1_effective_control_v1": 292.4813, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "b1-fraction-channels-mode", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "fraction_channels", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "fraction_channels"}, "expected_total": 2063.1062, "expected_b1": 1729.7158, "expected_b1_selected": "fraction_channels", "expected_b1_fraction_channels": 1729.7158, "expected_b1_aggregate_bias": 5849.625, "expected_b1_effective_control_v1": 292.4813, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} +{"label": "b1-aggregate-bias-mode", "calculator_version": "t3-bound-v1-bottou-refinement", "b1_model": "aggregate_bias", "inputs": {"gradient_fraction": 0.05, "gradient_norm_max": 1.0, "gradient_noise_stddev": 0.1, "lr_decision_interval": 100, "lr_grid_size": 8, "window_length": 10000, "batches_per_epoch": 1024, "steps_per_epoch": 1024, "c_b1": 1.0, "c_b2": 1.0, "c_b3": 1.0, "b1_model": "aggregate_bias"}, "expected_total": 6183.0154, "expected_b1": 5849.625, "expected_b1_selected": "aggregate_bias", "expected_b1_fraction_channels": 1729.7158, "expected_b1_aggregate_bias": 5849.625, "expected_b1_effective_control_v1": 292.4813, "expected_b2": 300.0, "expected_b3": 33.3904, "expected_snr_grad": 0.5, "expected_snr_per_channel": 10.0, "expected_certification_status": "NOT_CERTIFIED_BY_BOUND"} diff --git a/bench/scripts/t3_bound_calculator.py b/bench/scripts/t3_bound_calculator.py index ee1f4fb..c22b1ad 100644 --- a/bench/scripts/t3_bound_calculator.py +++ b/bench/scripts/t3_bound_calculator.py @@ -23,15 +23,24 @@ processing inequality; see §6 + §10). Operators with deployment- specific empirical measurements of the constants pass them via ``--c-b1`` / ``--c-b2`` / ``--c-b3`` flags. -**B1 model.** This v1 calculator uses ``b1_model="effective_control_v1"``: -``g`` is read as an *effective gradient-control coefficient* that -simultaneously bounds the fraction of steerable directions AND the -amplitude shrinkage of the aggregate adversarial gradient. Under that -reading the B1 formula ``C_B1 · g · W · log₂(1 + g·G/σ)`` is sound, -but it is **not the most-conservative gradient adversary** — see -#000036 §3 + §10 for the alternative ``b1_model="max_envelope"`` -path (deferred to a v2 bump). For now ``certification_status`` is -relative to this v1 effective-control model. +**B1 model.** Default ``b1_model="max_envelope"`` (#000036 §3.1, +dav1d review 2026-05-11): ``g`` has two competing interpretations — +*fraction of steerable directions* (each carrying full per-channel +SNR ``G/σ``) and *aggregate amplitude shrinkage* (one effective +channel carrying SNR ``g·G/σ``). The conservative envelope is the +larger of the two: + + B1_fraction_channels = C_B1 · g · W · log₂(1 + G/σ) + B1_aggregate_bias = C_B1 · W · log₂(1 + g·G/σ) + B1 (max_envelope) = max(B1_fraction_channels, B1_aggregate_bias) + +Other ``b1_model`` choices: ``"fraction_channels"`` / ``"aggregate_bias"`` +(the individual envelope terms) and ``"effective_control_v1"`` (the +older ``C_B1 · g · W · log₂(1 + g·G/σ)`` — an operational risk +score, NOT a worst-case bound; kept for backward comparison). +``certification_status`` is relative to whichever model is selected; +under the default ``max_envelope`` it is a genuine upper-bound +certification. Pure stdlib. No numpy / scipy dependency — the math is arithmetic + math.log2. @@ -58,7 +67,16 @@ import sys CALCULATOR_VERSION = "t3-bound-v1-bottou-refinement" -B1_MODEL = "effective_control_v1" +# B1 model selection (#000036 §3.1). Default is the conservative +# envelope; effective_control_v1 is the older non-worst-case formula +# kept for backward comparison. +B1_MODELS = ( + "max_envelope", + "fraction_channels", + "aggregate_bias", + "effective_control_v1", +) +B1_MODEL_DEFAULT = "max_envelope" CERTIFICATION_THRESHOLD_BITS = 256 @@ -100,13 +118,21 @@ def t3_bound_bits( c_b1: float = 1.0, c_b2: float = 1.0, c_b3: float = 1.0, + b1_model: str = B1_MODEL_DEFAULT, ) -> dict: """Compute the per-window mutual-information bound + per-bandwidth contributions per #000036 §6. - Returns a dict with the total bound, three per-bandwidth - contributions, the constants in use, and a sentence of operator - guidance based on the bound vs the SHA-256 (256-bit) baseline. + ``b1_model`` selects the gradient-bias model (see #000036 §3.1): + ``"max_envelope"`` (default, conservative), ``"fraction_channels"``, + ``"aggregate_bias"``, or ``"effective_control_v1"`` (older + non-worst-case formula). + + Returns a dict with the total bound, the per-bandwidth + contributions (including all three B1 model variants), the + constants + selected B1 model, a structured ``certification_status``, + and a sentence of operator guidance based on the bound vs the + SHA-256 (256-bit) certification threshold. """ # Validation — reject bools / NaN / inf; coerce numeric types. gradient_fraction = _require_finite_float("gradient_fraction", gradient_fraction) @@ -140,16 +166,41 @@ def t3_bound_bits( for name, c in [("c_b1", c_b1), ("c_b2", c_b2), ("c_b3", c_b3)]: if not 0 <= c <= 1: raise ValueError(f"{name} must be in [0, 1]; got {c}") + if b1_model not in B1_MODELS: + raise ValueError( + f"b1_model must be one of {B1_MODELS}; got {b1_model!r}" + ) # B1 — gradient bias. Per-step discrete channel-capacity bound on # the adversary-controlled parameter shift (NOT Fano's inequality — - # see #000036 §3): the per-step shift falls in one of ~SNR_grad+1 - # distinguishable buckets, capacity ≤ log₂(SNR_grad+1). Under the - # b1_model="effective_control_v1" reading, g bounds both the - # steerable-direction fraction and the aggregate amplitude shrinkage. - snr_grad = gradient_fraction * gradient_norm_max / gradient_noise_stddev - b1_per_step = math.log2(snr_grad + 1) - b1 = c_b1 * gradient_fraction * window_length * b1_per_step + # see #000036 §3): the per-step shift falls in one of ~SNR+1 + # distinguishable buckets, capacity ≤ log₂(SNR+1). Two SNR readings + # (#000036 §3.1): + # per-channel SNR = G/σ → fraction_channels model + # aggregate SNR = g·G/σ → aggregate_bias / effective_control + # Default b1_model="max_envelope" = max of the two envelope terms — + # genuinely upper-bounding across both interpretations (dav1d review + # 2026-05-11). + snr_grad = gradient_fraction * gradient_norm_max / gradient_noise_stddev # g·G/σ + snr_per_channel = gradient_norm_max / gradient_noise_stddev # G/σ + b1_fraction_channels = ( + c_b1 * gradient_fraction * window_length * math.log2(snr_per_channel + 1) + ) + b1_aggregate_bias = c_b1 * window_length * math.log2(snr_grad + 1) + b1_effective_control = ( + c_b1 * gradient_fraction * window_length * math.log2(snr_grad + 1) + ) + if b1_model == "max_envelope": + if b1_fraction_channels >= b1_aggregate_bias: + b1, b1_selected = b1_fraction_channels, "fraction_channels" + else: + b1, b1_selected = b1_aggregate_bias, "aggregate_bias" + elif b1_model == "fraction_channels": + b1, b1_selected = b1_fraction_channels, "fraction_channels" + elif b1_model == "aggregate_bias": + b1, b1_selected = b1_aggregate_bias, "aggregate_bias" + else: # effective_control_v1 + b1, b1_selected = b1_effective_control, "effective_control_v1" # B2 — LR selection. R-symbol categorical channel at every K-step # decision point (#000036 §4). @@ -210,12 +261,17 @@ def t3_bound_bits( return { "calculator_version": CALCULATOR_VERSION, - "b1_model": B1_MODEL, + "b1_model": b1_model, + "b1_selected": b1_selected, "I_window_bits_upper_bound": round(total, 4), "B1_contribution": round(b1, 4), + "B1_fraction_channels": round(b1_fraction_channels, 4), + "B1_aggregate_bias": round(b1_aggregate_bias, 4), + "B1_effective_control_v1": round(b1_effective_control, 4), "B2_contribution": round(b2, 4), "B3_contribution": round(b3, 4), "snr_grad": round(snr_grad, 4), + "snr_per_channel": round(snr_per_channel, 4), "decisions_in_window": decisions_in_window, "epochs_in_window": epochs_in_window, "constants": {"C_B1": c_b1, "C_B2": c_b2, "C_B3": c_b3}, @@ -224,7 +280,7 @@ def t3_bound_bits( "model_assumptions": [ "M2_nonce_per_window", "SHA256_random_oracle_baseline", - "B1_effective_control_model_v1", + f"B1_model_{b1_model}", "B3_gradient_noise_refinement", ], "inputs": { @@ -236,6 +292,10 @@ def t3_bound_bits( "window_length": window_length, "batches_per_epoch": batches_per_epoch, "steps_per_epoch": steps_per_epoch, + "c_b1": c_b1, + "c_b2": c_b2, + "c_b3": c_b3, + "b1_model": b1_model, }, "recommendation": recommendation, } @@ -245,7 +305,7 @@ def main(argv: list[str] | None = None) -> int: p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument( "--gradient-fraction", type=float, required=True, - help="g — effective gradient-control coefficient (b1_model=effective_control_v1); in [0, 1], 0 = no T2 surface", + help="g — gradient-control coefficient; in [0, 1], 0 = no T2 surface. Interpretation depends on --b1-model.", ) p.add_argument( "--gradient-norm-max", type=float, required=True, @@ -287,6 +347,15 @@ def main(argv: list[str] | None = None) -> int: "--c-b3", type=float, default=1.0, help="C_B3 constant; default 1.0", ) + p.add_argument( + "--b1-model", type=str, default=B1_MODEL_DEFAULT, choices=list(B1_MODELS), + help=( + "B1 gradient-bias model (#000036 §3.1). Default 'max_envelope' = " + "worst-case envelope across the two g-interpretations; " + "'fraction_channels' / 'aggregate_bias' = the individual terms; " + "'effective_control_v1' = older non-worst-case formula." + ), + ) args = p.parse_args(argv) try: @@ -302,6 +371,7 @@ def main(argv: list[str] | None = None) -> int: c_b1=args.c_b1, c_b2=args.c_b2, c_b3=args.c_b3, + b1_model=args.b1_model, ) except ValueError as e: print(f"input error: {e}", file=sys.stderr) diff --git a/docs/TICKETS.md b/docs/TICKETS.md index 6be24ed..965088a 100644 --- a/docs/TICKETS.md +++ b/docs/TICKETS.md @@ -101,7 +101,7 @@ Newest first. Update on every open/close. | #000039 | Optional `sqlite-vec` retrieval backend (A/B vs FTS5, hybrid not replacement) | open · awaiting go/no-go (doc-only Phase 0) | 2026-05-09 | — | | #000038 | Phase 4 content acquisition — proprietary textbook license decisions for warrant coverage | closed · obviated 2026-05-10 by alias-substitution sprint under #000031 (74 rows in #000041 + 13 rows in #000042); 92/92 records now resolve. Residue (multilingual PD, Hilbert-Ackermann OCR, Knuth permission, personal-copy path B) preserved as design log §8 | 2026-05-09 | — | | #000037 | Prometheus-Σ recursive falsification controller (bicameral substrate) | in progress · Phases 0 + 1 + 1.b + 1.c + 2 landed 2026-05-10; **§12 Trigger 2 fired** (divergence variance 0.575 / N=37); §22 Findings 2 + 3 RESOLVED (kernel/llm cost split + sweep_weights §15.4 + per-mode τ_qa); `controller_events` carries 4 event kinds (decision · difficulty · budget_allocation · falsification_proposal) feeding `arborist controller-events` inspector + live-harvest third bucket in `bench/scripts/harvest_falsification_proposals.py`; §12 Trigger 1 probe wired 2026-05-11 (`trigger_1_branch_density` reads `fork_score_branches` — measurable, not yet fired); Phase 3 sleep-sweep scheduler tracked under #000045 (gating ticket) | 2026-05-09 | — | -| #000036 | T3 per-window covert-channel budget bound | in progress · Phase 1 + dav1d review returned 2026-05-11; Tier-1 polish applied (wording → "CANNOT CERTIFY", structured `certification_status`/`b1_model` fields, bool/NaN/inf validation, `g=0` accepted, test cwd fix; 53 → 75 tests); **closure blockers: B1 conservative-envelope v2 calculator (awaits fox go/no-go) + active KAT fixture** | 2026-05-09 | — | +| #000036 | T3 per-window covert-channel budget bound | in progress · Phase 1 + dav1d review 2026-05-11 → Tier-1 + Tier-2 (Option B = `b1_model=max_envelope` default, applied *in v1*, no v2 fork) both landed 2026-05-11; KAT regenerated (12 entries, active); baseline 625.87 → 6183.02 (max_envelope); 53 → 83 tests; both prior closure blockers cleared — remaining = fox final close-or-iterate call | 2026-05-09 | — | | #000035 | PRG choice for φ_PRG (HMAC-SHA-512 expansion) | in progress · Phase 1 landed 2026-05-10; v7 §9.10 amendment awaits maintainer review | 2026-05-09 | — | | #000034 | Hessian alignment under φ_linear | in progress · Phase 1a landed 2026-05-10 (synthetic-ablation probe + KAT fixture); Phase 1b parks for v7 ramp-up | 2026-05-09 | — | | #000033 | Claim-pack pillar VII (combinatorics) | closed · landed 2026-05-09 (live in shard 000.db; lift verified) | 2026-05-09 | — | diff --git a/docs/calculator-test-patterns.md b/docs/calculator-test-patterns.md index 6e79907..8b925ac 100644 --- a/docs/calculator-test-patterns.md +++ b/docs/calculator-test-patterns.md @@ -13,7 +13,7 @@ bench across three modules: closed-form bound for #000036 The exemplar test file is ``tests/test_t3_bound_calculator.py`` -(75 cases as of 2026-05-10; fox shipped 51 in the +(83 cases as of 2026-05-10; fox shipped 51 in the initial cut and the +2 KAT-fixture-gap closure landed in ``581ad90``). The other two test files (``tests/test_anchor_prg.py`` and @@ -378,7 +378,7 @@ shape exposes the corresponding surface. Many calculator modules Exemplar test files: - ``tests/test_t3_bound_calculator.py`` — - 75 tests covering + 83 tests covering items 1-9 for the T3 closed-form bound. - ``tests/test_pi_star_arithmetic.py`` — 56 tests covering items diff --git a/docs/soft-hash-channel-t3-bound.md b/docs/soft-hash-channel-t3-bound.md index 0ef5312..b1b52fb 100644 --- a/docs/soft-hash-channel-t3-bound.md +++ b/docs/soft-hash-channel-t3-bound.md @@ -2,16 +2,18 @@ **Ticket**: #000036 **Source analysis**: `docs/soft-hash-channel-analysis.md` -**Date**: 2026-05-10 (review pass 2026-05-11) +**Date**: 2026-05-10 (dav1d review pass + Option-B applied 2026-05-11) **Status**: formal derivation + calculator landed; **dav1d review -returned 2026-05-11**. Tier-1 polish applied (recommendation -wording, structured `certification_status` output, validation -hardening, B1-model labelling — see §3.1 + §10.1). **Closure -blockers remaining**: B1 conservative-envelope (v2 calculator, -§10 item 1) and active KAT fixture (§10 item 2). The framework is -the deliverable; the named constants are conservative starting -estimates that empirical tightening (#000043) can replace without -changing call sites. +returned 2026-05-11** → **Tier-1 polish + Tier-2 (Option B: the +conservative `max_envelope` B1 model, applied in v1 — no v2 fork) +both landed 2026-05-11**. KAT fixture regenerated (12 entries, +active — `test_t3_bound_known_answer_tests` no longer skips). The +default B1 model is now `max_envelope` (§3.1); `effective_control_v1` +is reachable as a backward-comparison mode. Remaining before +closure: fox's final close-or-iterate call (optionally a dav1d +re-review of the envelope formula). The named C_B* constants are +conservative starting estimates that empirical tightening +(#000043) can replace without changing call sites. --- @@ -25,7 +27,7 @@ LR selection, batch order) and combines them into a closed-form bound consumed by `bench/scripts/t3_bound_calculator.py`. **dav1d's 2026-05-11 review** (`RESPONSE_1` + `RESPONSE_2`) -landed these findings: +findings and how they were resolved: 1. **§2 decomposition** — accepted. Markov-chain DPI on `A → Θ_{t+1} → C(M_{t+1})` correctly applied; T1+T2 baseline @@ -34,26 +36,32 @@ landed these findings: capacity bound. 3. **§5 (C_B3 batch order)** — accepted *as a model-bound, not a theorem* (§5's "model-bound" note reflects this). -4. **§3 (C_B1 gradient bias)** — **closure blocker**. `g` appears - twice in the per-window expression, which is only sound under - the `effective_control_v1` reading (§3.1). A worst-case - gradient adversary needs the `max_envelope` model — the v2 - calculator path (§10 item 1). +4. **§3 (C_B1 gradient bias)** — was the closure blocker. `g` + appeared twice in the per-window expression, only sound under + the `effective_control_v1` reading. **Resolved 2026-05-11**: + Option B applied in v1 — the default `b1_model` is now + `max_envelope` = `max(fraction_channels, aggregate_bias)`, + genuinely upper-bounding across both interpretations of `g` + (§3.1). `effective_control_v1` stays reachable for backward + comparison. All three B1 variants are reported in every output. 5. Wording / validation / output-schema fixes — applied (§10.1). **What's *not* being asked**: empirical validation against an -actual adversarial-training run (out of scope per §10 item 7; +actual adversarial-training run (out of scope per §10 item 5; opens as a future ticket once v7 plastic-training has a deployment target). -The headline numeric example in §7 + §11 yields -`I_window ≈ 626 bits/window` for a default deployment config -**under the v1 effective-control B1 model** — exceeding SHA-256's -256-bit threshold, so `certification_status = NOT_CERTIFIED_BY_BOUND`. -A conservative `max_envelope` B1 model would produce ~5850 bits. -The framework's job is to make the trigger derivable from -operator-measurable inputs; the v2 path makes the bound -worst-case-conservative rather than effective-control-relative. +The headline numeric example in §7 + §11 now yields +`I_window ≈ 6183 bits/window` for the default deployment config +**under the default `max_envelope` B1 model** (`B1 = aggregate_bias` += 5849.6 bits dominates `fraction_channels` = 1729.7 bits; +`effective_control_v1` would have been 292.5 bits) — well above +the 256-bit threshold, so `certification_status = NOT_CERTIFIED_BY_BOUND`. +That is the correct conservative reading: at W=10000 with these +inputs, the bound cannot certify M2 residual safety. The +calculator's `--b1-model` flag lets operators inspect the other +models if they want the effective-control risk score for +comparison. --- @@ -224,58 +232,49 @@ For typical deployments `g · ‖∇L_max‖ / σ_grad = 0.05 · 1 / 0.1 = 0.5`, so `log₂(1.5) ≈ 0.5850 bits/step`. With `W = 10000` and `g = 0.05`: `1 · 0.05 · 10000 · 0.5850 ≈ 292.48 bits/window`. -### §3.1 The B1 model — `g` appears twice (dav1d review 2026-05-11) +### §3.1 The B1 model — `max_envelope` (default) vs the alternatives -Note that `g` enters the per-window B1 expression **twice**: once -as the outer multiplier `g · W` (number of adversary-controlled +`g` enters the naive per-window B1 expression **twice**: once as +the outer multiplier `g · W` (number of adversary-controlled steps) and once inside `log₂(SNR_grad + 1)` where -`SNR_grad = g · ‖∇L_max‖ / σ_grad`. That double use is only sound -under a specific reading: +`SNR_grad = g · ‖∇L_max‖ / σ_grad`. dav1d's 2026-05-11 review +flagged that this double use is only sound under a *narrow* +interpretation of `g`. The calculator now offers four B1 models +(`--b1-model` flag), with `max_envelope` as the **default**: -``` -b1_model = "effective_control_v1": - g is an effective gradient-control coefficient that - simultaneously bounds (a) the fraction of steerable - directions per step AND (b) the amplitude shrinkage of - the aggregate adversarial gradient signal. -``` +| `b1_model` | Reading of `g` | Formula | Baseline B1 (`g=0.05, G=1, σ=0.1, W=10000`) | +|---|---|---|---| +| `fraction_channels` | fraction of steerable directions; each carries full per-channel SNR `G/σ` | `C_B1 · g · W · log₂(1 + G/σ)` | 1 729.7 bits | +| `aggregate_bias` | aggregate adversarial amplitude shrinkage; one effective channel carries SNR `g·G/σ` | `C_B1 · W · log₂(1 + g·G/σ)` | 5 849.6 bits | +| **`max_envelope`** (default) | take the worse of the two — no assumption about which interpretation holds | `max(fraction_channels, aggregate_bias)` | **5 849.6 bits** (`aggregate_bias` selected) | +| `effective_control_v1` | `g` simultaneously bounds *both* direction fraction AND amplitude shrinkage — an operational risk score, NOT a worst-case bound | `C_B1 · g · W · log₂(1 + g·G/σ)` | 292.5 bits | -If instead `g` means **only** "fraction of gradient computations -controlled" (with full per-channel amplitude), the conservative -shape is larger. dav1d's review spelled out the spread on the -baseline (`g=0.05, ‖∇L_max‖=1, σ_grad=0.1, W=10000`): +Every calculator output reports all three concrete variants +(`B1_fraction_channels`, `B1_aggregate_bias`, +`B1_effective_control_v1`), the selected one (`b1_selected`), and +both SNR readings (`snr_grad = g·G/σ`, `snr_per_channel = G/σ`), +regardless of which `b1_model` was requested — so a reader can +always see the spread. -| B1 model | Formula | Baseline B1 | -|---|---|---| -| `effective_control_v1` (current) | `g · W · log₂(1 + g·G/σ)` | **292.5 bits** | -| `fraction_channels` (g = channel fraction only) | `g · W · log₂(1 + G/σ)` | 1 729.7 bits | -| `aggregate_bias` (g = amplitude shrinkage only) | `W · log₂(1 + g·G/σ)` | 5 849.6 bits | -| `max_envelope` (conservative) | `max(fraction_channels, aggregate_bias)` | 5 849.6 bits | +**Why `max_envelope` is the default.** A security bound that may +be optimistic under some interpretation of its own inputs is not a +bound. `max_envelope` is upper-bounding across both readings of +`g`, so `certification_status` under the default is a genuine +certification. `effective_control_v1` is kept reachable for +operators who specifically want the older effective-control risk +score for comparison — it should never be used to *certify*. -The current v1 calculator uses `effective_control_v1`. The -`certification_status` it emits is therefore **relative to that -model** — it is an operational risk score, not a worst-case upper -bound across all gradient-adversary interpretations. - -**The conservative `max_envelope` model is the v2 path.** When the -team wants the calculator to certify against the worst-case -gradient adversary, it should: - -``` -1. add a b1_model parameter (default "max_envelope" for v2) -2. bump CALCULATOR_VERSION → "t3-bound-v2-conservative-envelope" -3. emit a new KAT fixture under - bench/fixtures/t3-bound/v2-conservative-envelope/ -4. keep effective_control_v1 reachable as a backward-compat mode - so v1 rows stay replayable -``` - -That v2 work is **not in this ticket** — see §10. Until it lands, -read the calculator's output as: "under the v1 effective-control -model, the baseline evaluates to ~626 bits/window; this exceeds -the 256-bit certification threshold so the calculator CANNOT -CERTIFY M2 residual safety at W=10000; a conservative B1 envelope -would produce a larger bound (~5850 bits in the example above)." +**Implementation note (dav1d Option B, applied in v1 2026-05-11).** +Per fox's direction, this was applied by changing the v1 +calculator's default rather than forking a v2 — `CALCULATOR_VERSION` +stays `t3-bound-v1-bottou-refinement` (the "bottou-refinement" +descriptor still names the unchanged B3 term). The `b1_model` field +appears in both the output and the echoed `inputs`, so KAT replays +are unambiguous about which model produced a given row. The KAT +fixture was regenerated 2026-05-11 (12 entries: the §7 worked +examples under `max_envelope` plus explicit `effective_control_v1` +/ `fraction_channels` / `aggregate_bias` mode pins and a `g=0` +edge case). --- @@ -384,50 +383,70 @@ remains Phase 2 / #000043. ## §6. Combined per-window bound -Combining §3-§5: +Combining §3-§5, with the **default `b1_model = max_envelope`** +(§3.1): ``` -I_window ≤ C_B1 · g · W · log₂( SNR_grad + 1 ) +B1 = C_B1 · max( g · W · log₂( 1 + ‖∇L_max‖ / σ_grad ), ← fraction_channels + W · log₂( 1 + g·‖∇L_max‖ / σ_grad ) ) ← aggregate_bias + +I_window ≤ B1 + C_B2 · ⌈W / K⌉ · log₂( R ) + C_B3 · ⌈W / E⌉ · log₂( N_b · σ_grad / ‖∇L_max‖ ) / 2 ``` with conservative constants `C_B1 = C_B2 = C_B3 = 1` (each by -data-processing inequality; future work may tighten any of them -based on empirical loss-surface measurement). +data-processing inequality; #000043 may tighten any of them based +on empirical measurement). For typical deployments `aggregate_bias` +dominates `fraction_channels` (because `1/g ≫ log₂(1+G/σ) / +log₂(1+g·G/σ)`), so `B1 ≈ W · log₂(1 + g·‖∇L_max‖/σ_grad)`. + +Other `b1_model` choices (`--b1-model`): `effective_control_v1` = +`C_B1 · g · W · log₂(1 + g·‖∇L_max‖/σ_grad)` — the older +non-worst-case effective-control risk score, ~`g`× smaller than +`aggregate_bias`; never use it to *certify*. --- ## §7. Numeric examples +All numbers are the calculator's closed-form output under the +default `b1_model = max_envelope`, not rounded estimates; see +`bench/scripts/t3_bound_calculator.py`, `tests/test_t3_bound_calculator.py`, +and the pinned KAT fixture `bench/fixtures/t3-bound/known-answer-tests.jsonl`. +The `effective_control_v1` column shows what the older +non-worst-case model would have produced, for comparison. + ### §7.1 Small deployment ``` g = 0.05, ‖∇L_max‖ = 1.0, σ_grad = 0.1 K = 100, R = 8, W = 10000, N_b = 1024, E = 1024 -SNR_grad = 0.5 +snr_grad = g·G/σ = 0.5 snr_per_channel = G/σ = 10.0 -B1 = 1 · 0.05 · 10000 · log₂(1.5) ≈ 292.48 bits -B2 = 1 · ⌈10000/100⌉ · log₂(8) = 300.00 bits -B3 = 1 · ⌈10000/1024⌉ · log₂(102.4) / 2 ≈ 33.39 bits -total ≈ 625.87 bits/window +B1_fraction_channels = 0.05 · 10000 · log₂(11) ≈ 1729.72 bits +B1_aggregate_bias = 10000 · log₂(1.5) ≈ 5849.63 bits ← selected (max) +B1 (max_envelope) ≈ 5849.63 bits +B2 = ⌈10000/100⌉ · log₂(8) = 300.00 bits +B3 = ⌈10000/1024⌉ · log₂(102.4) / 2 ≈ 33.39 bits +total ≈ 6183.02 bits/window + → NOT_CERTIFIED_BY_BOUND +(effective_control_v1 B1 would be ≈ 292.48 → total ≈ 625.87) ``` -(Numbers are the calculator's closed-form output, not rounded -estimates; see ``bench/scripts/t3_bound_calculator.py`` and the -unit tests in ``tests/test_t3_bound_calculator.py``.) - ### §7.2 Medium deployment (longer window) ``` g = 0.05, ‖∇L_max‖ = 1.0, σ_grad = 0.1 K = 1000, R = 16, W = 100000, N_b = 8192, E = 8192 -SNR_grad = 0.5 +snr_grad = 0.5 snr_per_channel = 10.0 -B1 = 1 · 0.05 · 100000 · log₂(1.5) ≈ 2924.81 bits -B2 = 1 · ⌈100000/1000⌉ · log₂(16) = 400.00 bits -B3 = 1 · ⌈100000/8192⌉ · log₂(819.2) / 2 ≈ 62.91 bits -total ≈ 3387.72 bits/window +B1_aggregate_bias = 100000 · log₂(1.5) ≈ 58496.25 bits ← selected +B2 = ⌈100000/1000⌉ · log₂(16) = 400.00 bits +B3 = ⌈100000/8192⌉ · log₂(819.2) / 2 ≈ 62.91 bits +total ≈ 58959.16 bits/window + → NOT_CERTIFIED_BY_BOUND +(effective_control_v1 B1 would be ≈ 2924.81 → total ≈ 3387.72) ``` ### §7.3 Hardened deployment (lower g, smaller R) @@ -435,54 +454,78 @@ total ≈ 3387.72 bits/window ``` g = 0.01, ‖∇L_max‖ = 1.0, σ_grad = 0.1 K = 100, R = 4, W = 10000, N_b = 1024, E = 1024 -SNR_grad = 0.1 +snr_grad = 0.1 snr_per_channel = 10.0 -B1 = 1 · 0.01 · 10000 · log₂(1.1) ≈ 13.75 bits -B2 = 1 · ⌈10000/100⌉ · log₂(4) = 200.00 bits -B3 = 1 · ⌈10000/1024⌉ · log₂(102.4) / 2 ≈ 33.39 bits -total ≈ 247.14 bits/window +B1_fraction_channels = 0.01 · 10000 · log₂(11) ≈ 345.94 bits +B1_aggregate_bias = 10000 · log₂(1.1) ≈ 1375.04 bits ← selected +B1 (max_envelope) ≈ 1375.04 bits +B2 = ⌈10000/100⌉ · log₂(4) = 200.00 bits +B3 = ⌈10000/1024⌉ · log₂(102.4) / 2 ≈ 33.39 bits +total ≈ 1608.43 bits/window + → NOT_CERTIFIED_BY_BOUND +(effective_control_v1 B1 would be ≈ 13.75 → total ≈ 247.14) ``` +Note: under the conservative `max_envelope` model, even the +"hardened" config exceeds 256 bits at W=10000 — the `aggregate_bias` +term `W · log₂(1 + g·G/σ)` grows with `W` regardless of how small +`g` is. Certifying M2 residual safety under this model requires a +**much smaller W** than the effective-control numbers suggested +(see §8). + --- ## §8. Operator guidance — choosing window length The operator picks `W` such that `I_window ≤ B_target` where -`B_target` is the desired residual. +`B_target` is the desired residual. Under the default +`max_envelope` model with the small-deployment constants +(`g=0.05, G=1, σ=0.1, K=100, R=8, E=1024`), `aggregate_bias` +dominates B1, so: + +``` +I_window ≈ W · log₂(1.5) + (W/100) · 3 + (W/1024) · 3.34/2 + = 0.5850 W + 0.0300 W + 0.00163 W + ≈ 0.6166 W bits/window +``` **Target = 1 bit/window** (very conservative; adversary needs `≥ 2^256` windows for a specific target): -Solving for `W` in §6 with the small-deployment constants: - ``` -1 ≥ 0.05 · W · 0.58 + (W/100) · 3 + (W/1024) · 3.34 / 2 -1 ≥ 0.029 W + 0.030 W + 0.0016 W -1 ≥ 0.061 W -W ≤ 16 steps (impractical — re-anchor every 16 steps) +1 ≥ 0.6166 W → W ≤ 1.6 steps (impractical — re-anchor essentially every step) ``` -**Target = 256 bits/window** (the SHA-256 hard-hash output size; -informational equivalent of a single brute-force preimage -attempt per window): +**Target = 256 bits/window** (the SHA-256 hard-hash output size): ``` -256 ≥ 0.061 W -W ≤ 4196 steps +256 ≥ 0.6166 W → W ≤ 415 steps ``` -This is the practical operating range: re-anchor approximately -every 4000 SGD steps to keep T3's per-window steerage at the -SHA-256 baseline. +This is the practical operating range under the conservative +model: re-anchor approximately every **400 SGD steps** to keep +T3's per-window steerage at the SHA-256 baseline. (The earlier +effective-control numbers gave ~4196 steps; the conservative +envelope is ~10× tighter, which is the price of not assuming +which interpretation of `g` holds. An operator who can *measure* +that the effective-control model applies to their deployment can +run `--b1-model effective_control_v1` and use the looser W — +but that is a calibration claim they must justify, not a default.) **Target = 2^16 = 65536 bits/window** (much larger window, adversary still needs ~2^240 windows to brute-force): ``` -W ≤ 1.07 × 10^6 steps +W ≤ 1.06 × 10^5 steps ``` -This admits week-long training runs between anchor rotations. +This admits roughly day-long training runs between anchor +rotations under the conservative model. + +Use `python -m bench.scripts.t3_bound_calculator` with your +deployment's `g / G / σ / K / R / W / N_b / E` to read off the +exact `certification_status` and per-bandwidth breakdown rather +than working the algebra above by hand. --- @@ -501,32 +544,20 @@ is the explicit form. Reference: this doc §6. ## §10. Open questions + future work -Items 1-2 below are the **closure blockers** flagged by dav1d's -2026-05-11 review; items 3-6 are tightening paths that refine -the bound without invalidating it. +**Closure-blocker status.** dav1d's 2026-05-11 review flagged two +blockers — the B1 model not being worst-case, and the KAT fixture +being skip-if-missing. **Both resolved 2026-05-11** (§10.1): +Option B applied *in v1* (per fox's direction — no v2 fork), so +the default `b1_model` is now `max_envelope` (§3.1); the KAT +fixture was regenerated (12 entries) and `test_t3_bound_known_answer_tests` +now runs rather than skips. What remains for closure is fox's +final close-or-iterate call (optionally a dav1d re-review of the +envelope formula itself). -1. **B1 conservative envelope (v2 calculator) — closure blocker.** - The current `b1_model="effective_control_v1"` (§3.1) reads `g` - as a combined direction-fraction + amplitude-shrinkage - coefficient, which makes the headline B1 ~292 bits on the - baseline. A worst-case gradient adversary needs the - `max_envelope` model (~5850 bits on the same baseline). Until - the v2 calculator with `b1_model` selection lands — - `CALCULATOR_VERSION → t3-bound-v2-conservative-envelope`, new - KAT fixture under `bench/fixtures/t3-bound/v2-conservative-envelope/`, - `effective_control_v1` kept as a backward-compat mode — the - calculator's `certification_status` is relative to the v1 - model only and should be read as an operational risk score, - not a certified worst-case bound. **Do not close #000036 - until this is resolved** (either by landing v2 or by an - explicit team decision to keep v1 with the narrowed-semantics - caveat documented in §3.1). -2. **Active KAT fixture for closure.** `bench/fixtures/t3-bound/known-answer-tests.jsonl` - must exist and `test_t3_bound_known_answer_tests` must run - (not skip) with ≥ 5 entries before #000036 closes. If the v2 - B1 model lands first, the KAT fixture goes under the - versioned subdirectory. -3. **C_B1 below the data-processing limit.** The discrete +Items 1-5 below are tightening paths that refine the bound +without invalidating it: + +1. **C_B1 below the data-processing limit.** The discrete channel-capacity bound `log₂(SNR_grad + 1)` is a per-step ceiling derived under uniform-prior signal levels; tighter bounds are possible if the loss surface has reduced @@ -534,22 +565,22 @@ the bound without invalidating it. deficiency). Empirical measurement via #000034's probe could tighten C_B1 by 1-2 orders of magnitude on typical deployments. (Tracked under #000043.) -4. **C_B2 below 1.** Many LR choices map to similar +2. **C_B2 below 1.** Many LR choices map to similar trajectories; a deployment-specific empirical measurement of LR-trajectory diversity (1-Wasserstein distance between `(LR_1, LR_2, … LR_R)` final-checkpoint distributions) yields C_B2 < 1. (Tracked under #000043.) -5. **C_B3 closer to the random-shuffle baseline.** If the +3. **C_B3 closer to the random-shuffle baseline.** If the deployment's SGD is random-shuffle (most modern training is), the adversarial-order bound used here over-estimates by `O(√N_b)` factor. C_B3 → `O(1/√N_b)`. (Tracked under #000043; the cheapest of the three constant-tightening paths — needs only a DataLoader-config audit, no checkpoint.) -6. **Future B4-B5 control surfaces.** Adaptive optimizer state +4. **Future B4-B5 control surfaces.** Adaptive optimizer state manipulation (momentum, second-moment estimates) is not in the §1 model. The framework here generalizes — add new B_i terms as new T3 control surfaces are documented. -7. **Empirical validation.** This bound has not been validated +5. **Empirical validation.** This bound has not been validated against an actual adversarial-training experiment. The acceptance criterion (§5 of the source ticket) explicitly marks empirical validation as out-of-scope; landing the @@ -557,31 +588,52 @@ the bound without invalidating it. #000034's Phase 1b would feed directly into a future empirical-validation ticket. -### §10.1 Calculator hardening landed 2026-05-11 (dav1d review) +### §10.1 dav1d-review changes landed 2026-05-11 -Not "open" — already done in the `8916bf3` follow-up + the -2026-05-11 review pass: +Not "open" — already done (Tier-1 in `da62f80`, Option B / Tier-2 +in the follow-up commit): +- **B1 model = `max_envelope` (Option B, applied in v1).** The + default `b1_model` takes `max(fraction_channels, aggregate_bias)` + — genuinely upper-bounding across both interpretations of `g` + (§3.1). `effective_control_v1` / `fraction_channels` / + `aggregate_bias` reachable via `--b1-model`. Every output reports + all three concrete B1 variants + `b1_selected` + both SNR + readings (`snr_grad`, `snr_per_channel`). `b1_model` is echoed in + `inputs` so KAT replays are unambiguous. Applied by changing the + v1 default rather than forking a v2 (per fox's direction): + `CALCULATOR_VERSION` stays `t3-bound-v1-bottou-refinement` (the + "bottou-refinement" descriptor names the unchanged B3 term). +- **KAT fixture regenerated** (`bench/fixtures/t3-bound/known-answer-tests.jsonl`, + 12 entries: §7 worked examples under `max_envelope`, explicit-mode + pins for the other three models, a `g=0` edge case). + `test_t3_bound_known_answer_tests` now runs (no longer skips); + it pins `b1_model`, `b1_selected`, `certification_status`, and + the per-contribution numbers. - Recommendation wording: "M2's single-window guarantee is broken" → "this conservative bound CANNOT CERTIFY M2's residual" (an upper bound exceeding 256 means we cannot certify, not that the adversary can steer 256 bits). -- Structured output fields: `b1_model`, `certification_status` - ∈ {`CERTIFIED_BY_BOUND`, `NOT_CERTIFIED_BY_BOUND`}, - `certification_threshold_bits`, `model_assumptions[]` — so - callers read a machine-readable status, not just prose. +- Structured output fields: `b1_model`, `b1_selected`, + `certification_status` ∈ {`CERTIFIED_BY_BOUND`, + `NOT_CERTIFIED_BY_BOUND`}, `certification_threshold_bits`, + `model_assumptions[]`, `B1_fraction_channels`, + `B1_aggregate_bias`, `B1_effective_control_v1`, `snr_per_channel` + — callers read a machine-readable status + the full B1 spread, + not just prose. - Input validation: bools rejected for both int and float fields (`isinstance(True, int)` is True in Python — a real leak risk for a security calculator); NaN / ±inf rejected for every - numeric input and constant. + numeric input and constant; invalid `b1_model` rejected. - `gradient_fraction = 0` now accepted (no T2 surface; B1 = 0; T3's LR + batch-order channels still contribute) — improves component isolation. - Tests: hard-coded `cwd="/home/fox/git/arborist"` replaced with `pathlib.Path(__file__).resolve().parents[1]` so the suite - runs on any checkout. New bool/NaN/inf rejection tests, - `g=0` acceptance test, `certification_status` field tests. - Suite count 53 → 75. + runs on any checkout. New bool/NaN/inf rejection tests, `g=0` + acceptance test, `certification_status` field tests, per-model + B1 hand-formula tests, `--b1-model` CLI test, invalid-`b1_model` + rejection test. Suite count 53 → 83. --- @@ -592,22 +644,24 @@ The closed-form bound from §6 lands as ```bash $ python -m bench.scripts.t3_bound_calculator \ - --gradient-fraction 0.05 \ - --gradient-norm-max 1.0 \ - --gradient-noise-stddev 0.1 \ - --lr-decision-interval 100 \ - --lr-grid-size 8 \ - --window-length 10000 \ - --batches-per-epoch 1024 \ - --steps-per-epoch 1024 + --gradient-fraction 0.05 --gradient-norm-max 1.0 \ + --gradient-noise-stddev 0.1 --lr-decision-interval 100 \ + --lr-grid-size 8 --window-length 10000 \ + --batches-per-epoch 1024 --steps-per-epoch 1024 + # [--b1-model max_envelope] ← the default { "calculator_version": "t3-bound-v1-bottou-refinement", - "b1_model": "effective_control_v1", - "I_window_bits_upper_bound": 625.8716, - "B1_contribution": 292.4813, + "b1_model": "max_envelope", + "b1_selected": "aggregate_bias", + "I_window_bits_upper_bound": 6183.0154, + "B1_contribution": 5849.625, + "B1_fraction_channels": 1729.7158, + "B1_aggregate_bias": 5849.625, + "B1_effective_control_v1": 292.4813, "B2_contribution": 300.0, "B3_contribution": 33.3904, "snr_grad": 0.5, + "snr_per_channel": 10.0, "decisions_in_window": 100, "epochs_in_window": 10, "constants": {"C_B1": 1.0, "C_B2": 1.0, "C_B3": 1.0}, @@ -616,11 +670,11 @@ $ python -m bench.scripts.t3_bound_calculator \ "model_assumptions": [ "M2_nonce_per_window", "SHA256_random_oracle_baseline", - "B1_effective_control_model_v1", + "B1_model_max_envelope", "B3_gradient_noise_refinement" ], - "inputs": { "...echoed input tuple..." }, - "recommendation": "I_window upper bound ≈ 625.9 bits/window + "inputs": { "...echoed input tuple incl. c_b1/c_b2/c_b3 + b1_model..." }, + "recommendation": "I_window upper bound ≈ 6183.0 bits/window EXCEEDS the SHA-256 (256 bit) certification threshold. This conservative bound CANNOT CERTIFY M2's single-window residual at this W @@ -632,14 +686,16 @@ $ python -m bench.scripts.t3_bound_calculator \ } ``` -The above example shows a deployment whose per-window bound -exceeds 256 bits **under the `effective_control_v1` B1 model** — -`certification_status` is `NOT_CERTIFIED_BY_BOUND` and the W of -10000 is too large for a 1-window SHA-256-resistance certification. -A conservative `max_envelope` B1 model (§3.1) would produce a -larger bound (~5850 bits in the worked example). Operators read -the structured `certification_status` field, not just the prose, -and adjust W (or g, R, K) to tune. +The above shows the default `max_envelope` model: `B1 = +aggregate_bias` = 5849.6 bits (the larger of the two envelope +terms; `fraction_channels` = 1729.7) → total 6183.0 bits/window, +`NOT_CERTIFIED_BY_BOUND` at W=10000. All three B1 variants +(including `B1_effective_control_v1` = 292.5, what the older +risk-score model would have given) are reported regardless of +which `b1_model` is selected. Pass `--b1-model effective_control_v1` +(or `fraction_channels` / `aggregate_bias`) to switch the selected +term. Operators read the structured `certification_status` field, +not just the prose, and adjust W (or g, R, K) to tune — see §8. --- @@ -660,5 +716,6 @@ and adjust W (or g, R, K) to tune. stability framework the §5 C_B3 random-shuffle reasoning draws on. - dav1d review 2026-05-11 (`RESPONSE_1` + `RESPONSE_2`) — the B1-double-`g` finding (§3.1), the recommendation-wording - correction, validation hardening, and the v2 conservative- - envelope path (§10 item 1). + correction, validation hardening, and the conservative + `max_envelope` B1 model. All applied in v1 2026-05-11 (Tier-1 + + Option B); see §3.1 + §10.1. diff --git a/docs/tickets/ticket-000036-t3-per-window-bound.md b/docs/tickets/ticket-000036-t3-per-window-bound.md index 2e7066f..06a36ff 100644 --- a/docs/tickets/ticket-000036-t3-per-window-bound.md +++ b/docs/tickets/ticket-000036-t3-per-window-bound.md @@ -1,6 +1,6 @@ # Ticket #000036 — T3 per-window covert-channel budget bound -**Status:** in progress · Phase 1 (formal derivation + calculator) landed 2026-05-10; **dav1d review returned 2026-05-11** (`RESPONSE_1` + `RESPONSE_2`); **Tier-1 polish applied 2026-05-11** (recommendation wording → "CANNOT CERTIFY", structured `certification_status` / `b1_model` / `model_assumptions` output fields, bool/NaN/inf validation hardening, `g=0` accepted, hard-coded test cwd removed; test suite 53 → 75); **closure blockers remaining**: (1) B1 conservative-envelope = v2 calculator (`b1_model=max_envelope`, `CALCULATOR_VERSION → t3-bound-v2-conservative-envelope`, versioned KAT fixture) — awaits fox go/no-go on Tier 2; (2) active KAT fixture (`bench/fixtures/t3-bound/known-answer-tests.jsonl` must exist + test must run not skip, ≥ 5 entries). Empirical constant-tightening tracked separately under #000043. R2's architectural integrations (Merkle audit-event commitment, SQD canonicalization, CTI clause-lattice, 5F trigger, ForkScore security-risk) are out-of-scope here — separate tickets if wanted. +**Status:** in progress · Phase 1 (formal derivation + calculator) landed 2026-05-10; **dav1d review returned 2026-05-11** (`RESPONSE_1` + `RESPONSE_2`) → **Tier-1 + Tier-2 both applied 2026-05-11**. Tier-1: recommendation wording → "CANNOT CERTIFY", structured `certification_status` / `b1_model` / `b1_selected` / `model_assumptions` output fields, bool/NaN/inf validation hardening, `g=0` accepted, hard-coded test cwd removed. **Tier-2 = dav1d Option B applied *in v1* (per fox — no v2 fork)**: default `b1_model` is now `max_envelope` = `max(fraction_channels, aggregate_bias)` (genuinely upper-bounding across both interpretations of `g`); `effective_control_v1` / `fraction_channels` / `aggregate_bias` reachable via `--b1-model`; every output reports all three B1 variants + both SNR readings; `CALCULATOR_VERSION` unchanged (`t3-bound-v1-bottou-refinement` — the descriptor names the unchanged B3 term, and `b1_model` echoed in `inputs` disambiguates KAT replays). **KAT fixture regenerated 2026-05-11** (12 entries; `test_t3_bound_known_answer_tests` now runs, not skips). Baseline I_window: 625.87 (effective_control_v1) → **6183.02 (max_envelope default)**. Test suite 53 → 83. **Both prior closure blockers cleared**; remaining = fox's final close-or-iterate call (optionally a dav1d re-review of the envelope formula). Empirical constant-tightening tracked separately under #000043. R2's architectural integrations (Merkle audit-event commitment, SQD canonicalization, CTI clause-lattice, 5F trigger, ForkScore security-risk) out-of-scope here — separate tickets if wanted. **Opened:** 2026-05-09 **Scope:** Derive an explicit closed-form upper bound on the covert- channel capacity under threat model T3 (hyperparameter adversary), diff --git a/docs/warrant-substrate-cookbook.md b/docs/warrant-substrate-cookbook.md index fe79bc6..febc4db 100644 --- a/docs/warrant-substrate-cookbook.md +++ b/docs/warrant-substrate-cookbook.md @@ -600,7 +600,7 @@ than waiting for bench-time STRICT-rate drift to surface it. W concentration + dim_h, closure (a_top + a_bot ≡ full-spectrum on dense decomposition), Lanczos eigenvalue ordering invariant. -- `tests/test_t3_bound_calculator.py` — **75 tests** (was 51; +- `tests/test_t3_bound_calculator.py` — **83 tests** (was 51; +2 from `581ad90` 2026-05-10 KAT-fixture-gap closure) for the T3 per-window covert-channel bound calculator (#000036 §11); pins the closed-form B1/B2/B3 formulas, monotonicity in each @@ -674,7 +674,7 @@ than waiting for bench-time STRICT-rate drift to surface it. | aliases.py | 512 | 469 (28 tests) | 0.92 | | warrant_resolver.py | ~800 | ~430 (combined) | 0.54 | | warrant_chain.py | 89 | 320 (9 tests) | 3.6 | -| t3_bound_calculator.py | 249 | 446 (75 tests) | 1.79 | +| t3_bound_calculator.py | 249 | 446 (83 tests) | 1.79 | | fork_score.py | 386 | 609 (23 tests) | 1.58 | | weights.py | 73 | 180 (16 tests) | 2.5 | | pi_star/protocol+registry | 124 | 280 (21 tests) | 2.3 | diff --git a/tests/test_t3_bound_calculator.py b/tests/test_t3_bound_calculator.py index addc3d2..730cfbf 100644 --- a/tests/test_t3_bound_calculator.py +++ b/tests/test_t3_bound_calculator.py @@ -51,32 +51,45 @@ _BASELINE = dict( def test_baseline_matches_section_11_doc(): - """The §11 worked example baseline. Calculator's actual numbers: + """The §11 worked example baseline under the default b1_model= + "max_envelope" (dav1d review 2026-05-11). Calculator's actual numbers: - I_window_bits_upper_bound: 625.8716 - B1_contribution: 292.4813 - B2_contribution: 300.0 - B3_contribution: 33.3904 - snr_grad: 0.5 + I_window_bits_upper_bound: 6183.0154 + B1_contribution: 5849.625 (aggregate_bias selected) + B1_fraction_channels: 1729.7158 + B1_aggregate_bias: 5849.625 + B1_effective_control_v1: 292.4813 (the older non-worst-case) + B2_contribution: 300.0 + B3_contribution: 33.3904 + snr_grad: 0.5 (g·G/σ) + snr_per_channel: 10.0 (G/σ) - Doc §11's quoted 622.7 / 290.0 / 32.7 are first-cut rounded - estimates; calculator's actual closed-form values differ by - ~3 bits total due to the doc's 290 vs actual 292.5 in B1 and - 32.7 vs actual 33.4 in B3. This test pins the closed-form - truth (the doc text needs a calibration pass; tracked in this - test's docstring as a follow-up). + The conservative envelope takes max(fraction_channels, aggregate_bias); + at this baseline aggregate_bias (5849.6) dominates fraction_channels + (1729.7). The old effective_control formula (292.5) is still reported + as B1_effective_control_v1 for comparison. """ r = t3_bound_bits(**_BASELINE) - # Total: ⟨B1+B2+B3⟩ ≈ 292.48 + 300.0 + 33.39 = 625.87 - assert r["I_window_bits_upper_bound"] == pytest.approx(625.87, abs=0.05) + assert r["b1_model"] == "max_envelope" + assert r["b1_selected"] == "aggregate_bias" + # Total: ⟨B1+B2+B3⟩ ≈ 5849.63 + 300.0 + 33.39 = 6183.02 + assert r["I_window_bits_upper_bound"] == pytest.approx(6183.02, abs=0.05) # B2 is exact: ⌈10000/100⌉ · log₂(8) = 100 · 3 = 300 assert r["B2_contribution"] == pytest.approx(300.0, abs=1e-3) # B3: epochs = ⌈10000/1024⌉ = 10; per-epoch factor = 1024·0.1/1.0 = 102.4; # log₂(102.4)/2 ≈ 3.339; total ≈ 33.39 bits. assert r["B3_contribution"] == pytest.approx(33.39, abs=0.05) - # B1: C_B1 · g · W · log₂(SNR+1) where SNR = 0.05·1.0/0.1 = 0.5 - # → log₂(1.5) ≈ 0.585; total ≈ 1.0 · 0.05 · 10000 · 0.585 = 292.48 - assert r["B1_contribution"] == pytest.approx(292.48, abs=0.05) + # B1 (max_envelope): aggregate_bias = W·log₂(1 + g·G/σ) = 10000·log₂(1.5) + # ≈ 10000·0.585 = 5849.63 (> fraction_channels 1729.72). + assert r["B1_contribution"] == pytest.approx(5849.63, abs=0.05) + assert r["B1_aggregate_bias"] == pytest.approx(5849.63, abs=0.05) + # fraction_channels = g·W·log₂(1 + G/σ) = 0.05·10000·log₂(11) ≈ 1729.72 + assert r["B1_fraction_channels"] == pytest.approx(1729.72, abs=0.05) + # effective_control_v1 (old formula) still reported: g·W·log₂(1+g·G/σ) + # = 0.05·10000·log₂(1.5) ≈ 292.48 + assert r["B1_effective_control_v1"] == pytest.approx(292.48, abs=0.05) + assert r["snr_grad"] == pytest.approx(0.5, abs=1e-6) # g·G/σ + assert r["snr_per_channel"] == pytest.approx(10.0, abs=1e-6) # G/σ def test_returns_calculator_version_token(): @@ -289,15 +302,17 @@ def test_recommendation_total_le_zero(): def test_recommendation_below_sha256(): - """Baseline (~622 bits) actually exceeds 256 — wrong band for - this test. Use a config that lands in the (0, 256) range.""" - args = {**_BASELINE, "window_length": 1000} + """The default-baseline bound exceeds 256 under max_envelope; use a + small W that lands in the (0, 256) certified band. At W=300: + aggregate_bias ≈ 175.5, B2 = 9, B3 ≈ 3.3 → total ≈ 187.8.""" + args = {**_BASELINE, "window_length": 300} r = t3_bound_bits(**args) assert 0 < r["I_window_bits_upper_bound"] < 256, ( - f"window_length=1000 should land in (0, 256); got {r['I_window_bits_upper_bound']}" + f"window_length=300 should land in (0, 256); got {r['I_window_bits_upper_bound']}" ) assert "windows" in r["recommendation"] assert "256" in r["recommendation"] + assert r["certification_status"] == "CERTIFIED_BY_BOUND" def test_recommendation_exceeds_sha256(): @@ -315,22 +330,29 @@ def test_recommendation_exceeds_sha256(): def test_output_carries_b1_model_and_certification_fields(): - """Structured machine-readable fields added per dav1d review - 2026-05-11: b1_model, certification_status, - certification_threshold_bits, model_assumptions.""" + """Structured machine-readable fields (dav1d review 2026-05-11): + b1_model, b1_selected, certification_status, + certification_threshold_bits, model_assumptions, plus all three + B1 model variants + both SNR readings.""" r = t3_bound_bits(**_BASELINE) - assert r["b1_model"] == "effective_control_v1" + assert r["b1_model"] == "max_envelope" # the new default + assert r["b1_selected"] in {"fraction_channels", "aggregate_bias"} assert r["certification_threshold_bits"] == 256 assert r["certification_status"] in { "CERTIFIED_BY_BOUND", "NOT_CERTIFIED_BY_BOUND" } - assert "B1_effective_control_model_v1" in r["model_assumptions"] + assert "B1_model_max_envelope" in r["model_assumptions"] + # all three B1 variants reported regardless of which is selected + for k in ("B1_fraction_channels", "B1_aggregate_bias", + "B1_effective_control_v1", "snr_per_channel"): + assert k in r, f"missing output field {k!r}" assert r["certification_status"] == "NOT_CERTIFIED_BY_BOUND" # baseline > 256 def test_certification_status_certified_below_threshold(): - """A config landing in (0, 256) → CERTIFIED_BY_BOUND.""" - r = t3_bound_bits(**{**_BASELINE, "window_length": 1000}) + """A config landing in (0, 256) → CERTIFIED_BY_BOUND. W=300 under + max_envelope yields ~188 bits.""" + r = t3_bound_bits(**{**_BASELINE, "window_length": 300}) assert 0 < r["I_window_bits_upper_bound"] < 256 assert r["certification_status"] == "CERTIFIED_BY_BOUND" @@ -357,8 +379,34 @@ def test_cli_baseline_runs_clean(tmp_path): cwd=REPO_ROOT) j = json.loads(out.stdout) assert j["calculator_version"] == CALCULATOR_VERSION - # Closed-form actual; doc §11's 622.7 is approximate. - assert j["I_window_bits_upper_bound"] == pytest.approx(625.87, abs=0.05) + assert j["b1_model"] == "max_envelope" # CLI default + # Closed-form actual under max_envelope: 5849.63 + 300.0 + 33.39 = 6183.02 + assert j["I_window_bits_upper_bound"] == pytest.approx(6183.02, abs=0.05) + + +def test_cli_b1_model_flag(): + """--b1-model effective_control_v1 reverts to the older formula; + --b1-model fraction_channels picks that envelope term.""" + base_cmd = [ + sys.executable, "-m", "bench.scripts.t3_bound_calculator", + "--gradient-fraction", "0.05", "--gradient-norm-max", "1.0", + "--gradient-noise-stddev", "0.1", "--lr-decision-interval", "100", + "--lr-grid-size", "8", "--window-length", "10000", + "--batches-per-epoch", "1024", "--steps-per-epoch", "1024", + ] + for model, expected_b1 in [ + ("effective_control_v1", 292.48), + ("fraction_channels", 1729.72), + ("aggregate_bias", 5849.63), + ]: + out = subprocess.run(base_cmd + ["--b1-model", model], + capture_output=True, text=True, check=True, + cwd=REPO_ROOT) + j = json.loads(out.stdout) + assert j["b1_model"] == model + assert j["B1_contribution"] == pytest.approx(expected_b1, abs=0.05), ( + f"b1_model={model}: B1={j['B1_contribution']} vs expected {expected_b1}" + ) def test_cli_invalid_input_exits_2(): @@ -404,24 +452,59 @@ def test_b2_exact_formula(): ) -def test_b1_exact_formula(): - """B1 = C_B1 · g · W · log₂(SNR + 1) where SNR = g·‖∇L_max‖/σ_grad. +def test_b1_effective_control_v1_exact_formula(): + """b1_model="effective_control_v1": B1 = C_B1 · g · W · log₂(SNR+1) + where SNR = g·‖∇L_max‖/σ_grad (the older non-worst-case formula). Hand-compute for one config and check exact agreement.""" g, gnm, sigma, W = 0.1, 2.0, 0.5, 5000 snr = g * gnm / sigma # 0.4 expected = 1.0 * g * W * math.log2(snr + 1) r = t3_bound_bits(**{ **_BASELINE, - "gradient_fraction": g, - "gradient_norm_max": gnm, - "gradient_noise_stddev": sigma, - "window_length": W, + "gradient_fraction": g, "gradient_norm_max": gnm, + "gradient_noise_stddev": sigma, "window_length": W, + "b1_model": "effective_control_v1", }) - # Output rounded to 4 dp inside the calculator. + assert r["b1_model"] == "effective_control_v1" + assert r["b1_selected"] == "effective_control_v1" assert r["B1_contribution"] == pytest.approx(expected, abs=1e-3) + assert r["B1_effective_control_v1"] == pytest.approx(expected, abs=1e-3) assert r["snr_grad"] == pytest.approx(snr, abs=1e-3) +def test_b1_max_envelope_exact_formula(): + """b1_model="max_envelope" (default): B1 = max(fraction_channels, + aggregate_bias) where + fraction_channels = C_B1 · g · W · log₂(1 + G/σ) + aggregate_bias = C_B1 · W · log₂(1 + g·G/σ) + Hand-compute for one config; verify the larger term is selected.""" + g, gnm, sigma, W = 0.1, 2.0, 0.5, 5000 + snr_per_channel = gnm / sigma # 4.0 + snr_grad = g * gnm / sigma # 0.4 + fc = 1.0 * g * W * math.log2(snr_per_channel + 1) # 0.1·5000·log₂(5) ≈ 1160.96 + ab = 1.0 * W * math.log2(snr_grad + 1) # 5000·log₂(1.4) ≈ 2426.26 + expected_b1 = max(fc, ab) + expected_selected = "aggregate_bias" if ab >= fc else "fraction_channels" + r = t3_bound_bits(**{ + **_BASELINE, + "gradient_fraction": g, "gradient_norm_max": gnm, + "gradient_noise_stddev": sigma, "window_length": W, + # b1_model defaults to max_envelope + }) + assert r["b1_model"] == "max_envelope" + assert r["b1_selected"] == expected_selected + assert r["B1_contribution"] == pytest.approx(expected_b1, abs=1e-3) + assert r["B1_fraction_channels"] == pytest.approx(fc, abs=1e-3) + assert r["B1_aggregate_bias"] == pytest.approx(ab, abs=1e-3) + assert r["snr_per_channel"] == pytest.approx(snr_per_channel, abs=1e-3) + + +@pytest.mark.parametrize("bad", ["v2", "envelope", "", "MAX_ENVELOPE", None, 1]) +def test_invalid_b1_model_rejected(bad): + with pytest.raises(ValueError, match="b1_model"): + t3_bound_bits(**{**_BASELINE, "b1_model": bad}) + + # --- integration with §6 framework ----------------------------------- @@ -504,6 +587,21 @@ def test_t3_bound_known_answer_tests(): Closes the calculator-test-patterns.md §1 checklist gap (no KAT fixture for this calculator). """ + # Numeric fields checked on every KAT entry. + _NUMERIC = [ + ("expected_total", "I_window_bits_upper_bound"), + ("expected_b1", "B1_contribution"), + ("expected_b2", "B2_contribution"), + ("expected_b3", "B3_contribution"), + ("expected_snr_grad", "snr_grad"), + ] + # Optional numeric fields (present in 2026-05-11+ fixtures). + _NUMERIC_OPT = [ + ("expected_b1_fraction_channels", "B1_fraction_channels"), + ("expected_b1_aggregate_bias", "B1_aggregate_bias"), + ("expected_b1_effective_control_v1", "B1_effective_control_v1"), + ("expected_snr_per_channel", "snr_per_channel"), + ] n_kats = 0 for line in KAT_FIXTURE.read_text(encoding="utf-8").splitlines(): line = line.strip() @@ -517,13 +615,27 @@ def test_t3_bound_known_answer_tests(): f"recorded {kat['calculator_version']}, " f"function {r['calculator_version']}" ) - for field, key in [ - ("expected_total", "I_window_bits_upper_bound"), - ("expected_b1", "B1_contribution"), - ("expected_b2", "B2_contribution"), - ("expected_b3", "B3_contribution"), - ("expected_snr_grad", "snr_grad"), - ]: + # b1_model + b1_selected pinned when recorded. + if "b1_model" in kat: + assert r["b1_model"] == kat["b1_model"], ( + f"KAT b1_model mismatch on {kat['label']!r}: " + f"recorded {kat['b1_model']}, function {r['b1_model']}" + ) + if "expected_b1_selected" in kat: + assert r["b1_selected"] == kat["expected_b1_selected"], ( + f"KAT b1_selected mismatch on {kat['label']!r}: " + f"recorded {kat['expected_b1_selected']}, " + f"function {r['b1_selected']}" + ) + if "expected_certification_status" in kat: + assert r["certification_status"] == kat["expected_certification_status"], ( + f"KAT certification_status mismatch on {kat['label']!r}: " + f"recorded {kat['expected_certification_status']}, " + f"function {r['certification_status']}" + ) + for field, key in _NUMERIC + _NUMERIC_OPT: + if field not in kat: + continue recorded = float(kat[field]) observed = float(r[key]) assert observed == pytest.approx(recorded, abs=1e-3), (